Merge "Fix root RenderNode damage calculation" into lmp-dev
diff --git a/Android.mk b/Android.mk
index 8a50ae84..e2fa7d1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -118,9 +118,7 @@
 	core/java/android/content/IIntentReceiver.aidl \
 	core/java/android/content/IIntentSender.aidl \
 	core/java/android/content/IOnPrimaryClipChangedListener.aidl \
-        core/java/android/content/IPermissionResponseCallback.aidl \
 	core/java/android/content/IRestrictionsManager.aidl \
-        core/java/android/content/IRestrictionsProvider.aidl \
 	core/java/android/content/ISyncAdapter.aidl \
 	core/java/android/content/ISyncContext.aidl \
 	core/java/android/content/ISyncServiceAdapter.aidl \
diff --git a/api/current.txt b/api/current.txt
index 8076d47..6da3365 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -359,6 +359,7 @@
     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 calendarTextColor = 16843934; // 0x101049e
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
     field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
@@ -447,6 +448,14 @@
     field public static final int dashWidth = 16843174; // 0x10101a6
     field public static final int data = 16842798; // 0x101002e
     field public static final int datePickerStyle = 16843612; // 0x101035c
+    field public static final int dateSelectorBackgroundColor = 16843928; // 0x1010498
+    field public static final int dateSelectorDayOfMonthTextAppearance = 16843930; // 0x101049a
+    field public static final int dateSelectorDayOfWeekBackgroundColor = 16843926; // 0x1010496
+    field public static final int dateSelectorDayOfWeekTextAppearance = 16843927; // 0x1010497
+    field public static final int dateSelectorMonthTextAppearance = 16843929; // 0x1010499
+    field public static final int dateSelectorYearListItemTextAppearance = 16843932; // 0x101049c
+    field public static final int dateSelectorYearListSelectedCircleColor = 16843933; // 0x101049d
+    field public static final int dateSelectorYearTextAppearance = 16843931; // 0x101049b
     field public static final int dateTextAppearance = 16843593; // 0x1010349
     field public static final int debuggable = 16842767; // 0x101000f
     field public static final int defaultValue = 16843245; // 0x10101ed
@@ -742,6 +751,8 @@
     field public static final int largeScreens = 16843398; // 0x1010286
     field public static final int largestWidthLimitDp = 16843622; // 0x1010366
     field public static final int launchMode = 16842781; // 0x101001d
+    field public static final int launchTaskBehindBackgroundAnimation = 16843923; // 0x1010493
+    field public static final int launchTaskBehindSourceAnimation = 16843924; // 0x1010494
     field public static final int layerType = 16843604; // 0x1010354
     field public static final int layout = 16842994; // 0x10100f2
     field public static final int layoutAnimation = 16842988; // 0x10100ec
@@ -996,6 +1007,7 @@
     field public static final int restoreAnyVersion = 16843450; // 0x10102ba
     field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d
     field public static final int restrictedAccountType = 16843733; // 0x10103d5
+    field public static final int restrictionType = 16843925; // 0x1010495
     field public static final int reversible = 16843853; // 0x101044d
     field public static final int right = 16843183; // 0x10101af
     field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
@@ -3664,6 +3676,7 @@
 
   public class ActivityOptions {
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
+    method public static android.app.ActivityOptions makeLaunchTaskBehindAnimation();
     method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, java.lang.String);
     method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View, java.lang.String>...);
@@ -5292,6 +5305,8 @@
     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);
@@ -6575,11 +6590,10 @@
 
 package android.content {
 
-  public abstract class AbstractRestrictionsProvider extends android.app.Service {
+  public abstract class AbstractRestrictionsProvider extends android.content.BroadcastReceiver {
     ctor public AbstractRestrictionsProvider();
-    method public abstract android.os.Bundle getPermissionResponse(java.lang.String, java.lang.String);
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract void requestPermission(java.lang.String, java.lang.String, android.os.Bundle);
+    method public void onReceive(android.content.Context, android.content.Intent);
+    method public abstract void requestPermission(android.content.Context, java.lang.String, java.lang.String, android.os.Bundle);
   }
 
   public abstract class AbstractThreadedSyncAdapter {
@@ -7050,6 +7064,7 @@
     method public abstract android.content.res.AssetManager getAssets();
     method public abstract java.io.File getCacheDir();
     method public abstract java.lang.ClassLoader getClassLoader();
+    method public abstract java.io.File getCodeCacheDir();
     method public abstract android.content.ContentResolver getContentResolver();
     method public abstract java.io.File getDatabasePath(java.lang.String);
     method public abstract java.io.File getDir(java.lang.String, int);
@@ -7176,6 +7191,7 @@
     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";
@@ -7221,6 +7237,7 @@
     method public android.content.Context getBaseContext();
     method public java.io.File getCacheDir();
     method public java.lang.ClassLoader getClassLoader();
+    method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
@@ -7696,7 +7713,6 @@
     field public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; // 0x800000
     field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000
     field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000
-    field public static final int FLAG_ACTIVITY_LAUNCH_BEHIND = 4096; // 0x1000
     field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
     field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000
     field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
@@ -7924,6 +7940,7 @@
   }
 
   public class RestrictionEntry implements android.os.Parcelable {
+    ctor public RestrictionEntry(int, java.lang.String);
     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[]);
@@ -7958,31 +7975,34 @@
     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
+    field public static final int TYPE_STRING = 6; // 0x6
   }
 
   public class RestrictionsManager {
     method public android.os.Bundle getApplicationRestrictions();
     method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String);
-    method public void getPermissionResponse(java.lang.String, android.content.RestrictionsManager.PermissionResponseCallback);
     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 EXTRA_PACKAGE_NAME = "package_name";
-    field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "response";
+    field public static final java.lang.String ACTION_REQUEST_PERMISSION = "android.content.action.REQUEST_PERMISSION";
+    field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+    field public static final java.lang.String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
+    field public static final java.lang.String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
+    field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
+    field public static final java.lang.String META_DATA_APP_RESTRICTIONS = "android.content.APP_RESTRICTIONS";
     field public static final java.lang.String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label";
     field public static final java.lang.String REQUEST_KEY_DATA = "android.request.data";
     field public static final java.lang.String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
-    field public static final java.lang.String REQUEST_KEY_DEVICE_NAME = "android.request.device";
     field public static final java.lang.String REQUEST_KEY_ICON = "android.request.icon";
     field public static final java.lang.String REQUEST_KEY_ID = "android.request.id";
     field public static final java.lang.String REQUEST_KEY_MESSAGE = "android.request.mesg";
-    field public static final java.lang.String REQUEST_KEY_REQUESTOR_NAME = "android.request.requestor";
+    field public static final java.lang.String REQUEST_KEY_NEW_REQUEST = "android.request.new_request";
     field public static final java.lang.String REQUEST_KEY_TITLE = "android.request.title";
+    field public static final java.lang.String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
     field public static final java.lang.String REQUEST_TYPE_LOCAL_APPROVAL = "android.request.type.local_approval";
-    field public static final java.lang.String REQUEST_TYPE_QUESTION = "android.request.type.question";
     field public static final java.lang.String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
-    field public static final java.lang.String RESPONSE_KEY_ERROR_MESSAGE = "android.response.errormsg";
+    field public static final java.lang.String RESPONSE_KEY_MESSAGE = "android.response.msg";
     field public static final java.lang.String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
     field public static final java.lang.String RESPONSE_KEY_RESULT = "android.response.result";
     field public static final int RESULT_APPROVED = 1; // 0x1
@@ -7995,11 +8015,6 @@
     field public static final int RESULT_UNKNOWN_REQUEST = 4; // 0x4
   }
 
-  public static abstract class RestrictionsManager.PermissionResponseCallback {
-    ctor public RestrictionsManager.PermissionResponseCallback();
-    method public abstract void onResponse(android.os.Bundle);
-  }
-
   public class SearchRecentSuggestionsProvider extends android.content.ContentProvider {
     ctor public SearchRecentSuggestionsProvider();
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
@@ -8901,7 +8916,7 @@
     ctor public AssetFileDescriptor.AutoCloseOutputStream(android.content.res.AssetFileDescriptor) throws java.io.IOException;
   }
 
-  public final class AssetManager {
+  public final class AssetManager implements java.lang.AutoCloseable {
     method public void close();
     method public final java.lang.String[] getLocales();
     method public final java.lang.String[] list(java.lang.String) throws java.io.IOException;
@@ -14417,6 +14432,7 @@
     method public final void release();
     method public final void releaseOutputBuffer(int, boolean);
     method public final void releaseOutputBuffer(int, long);
+    method public final void reset();
     method public void setCallback(android.media.MediaCodec.Callback);
     method public final void setParameters(android.os.Bundle);
     method public final void setVideoScalingMode(int);
@@ -14939,7 +14955,9 @@
     method public void attachAuxEffect(int);
     method public static android.media.MediaPlayer create(android.content.Context, android.net.Uri);
     method public static android.media.MediaPlayer create(android.content.Context, android.net.Uri, android.view.SurfaceHolder);
+    method public static android.media.MediaPlayer create(android.content.Context, android.net.Uri, android.view.SurfaceHolder, android.media.AudioAttributes, int);
     method public static android.media.MediaPlayer create(android.content.Context, int);
+    method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
     method public void deselectTrack(int) throws java.lang.IllegalStateException;
     method public int getAudioSessionId();
     method public int getCurrentPosition();
@@ -14957,6 +14975,7 @@
     method public void reset();
     method public void seekTo(int) throws java.lang.IllegalStateException;
     method public void selectTrack(int) throws java.lang.IllegalStateException;
+    method public void setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
     method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public void setAudioStreamType(int);
     method public void setAuxEffectSendLevel(float);
@@ -15601,7 +15620,7 @@
     method public final int getMaxVolume();
     method public final int getVolumeControl();
     method public final void notifyVolumeChanged();
-    method public void onAdjustVolumeBy(int);
+    method public void onAdjustVolume(int);
     method public abstract int onGetCurrentVolume();
     method public void onSetVolumeTo(int);
     field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
@@ -16203,12 +16222,12 @@
 package android.media.session {
 
   public final class MediaController {
+    ctor public MediaController(android.media.session.MediaSession.Token);
     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 void adjustVolume(int, int);
     method public android.media.routing.MediaRouter.Delegate createMediaRouterDelegate();
     method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
-    method public static android.media.session.MediaController fromToken(android.media.session.MediaSession.Token);
     method public android.media.MediaMetadata getMetadata();
     method public android.media.session.PlaybackState getPlaybackState();
     method public int getRatingType();
@@ -16248,6 +16267,7 @@
   }
 
   public final class MediaSession {
+    ctor public MediaSession(android.content.Context, java.lang.String);
     method public void addCallback(android.media.session.MediaSession.Callback);
     method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
     method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
@@ -16299,7 +16319,6 @@
 
   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);
   }
 
@@ -16358,6 +16377,29 @@
 
 package android.media.tv {
 
+  public class TvContentRating {
+    ctor public TvContentRating(java.lang.String);
+    ctor public TvContentRating(java.lang.String, java.lang.String[]);
+    method public java.lang.String flattenToString();
+    method public static android.media.tv.TvContentRating unflattenFromString(java.lang.String);
+    field public static final java.lang.String RATING_KR_12 = "RATING_KR_12";
+    field public static final java.lang.String RATING_KR_15 = "RATING_KR_15";
+    field public static final java.lang.String RATING_KR_19 = "RATING_KR_19";
+    field public static final java.lang.String RATING_KR_7 = "RATING_KR_7";
+    field public static final java.lang.String RATING_KR_ALL = "RATING_KR_ALL";
+    field public static final java.lang.String RATING_US_TV_14 = "RATING_US_TV_14";
+    field public static final java.lang.String RATING_US_TV_G = "RATING_US_TV_G";
+    field public static final java.lang.String RATING_US_TV_MA = "RATING_US_TV_MA";
+    field public static final java.lang.String RATING_US_TV_PG = "RATING_US_TV_PG";
+    field public static final java.lang.String RATING_US_TV_Y = "RATING_US_TV_Y";
+    field public static final java.lang.String RATING_US_TV_Y7 = "RATING_US_TV_Y7";
+    field public static final java.lang.String SUBRATING_US_D = "SUBRATING_US_D";
+    field public static final java.lang.String SUBRATING_US_FV = "SUBRATING_US_FV";
+    field public static final java.lang.String SUBRATING_US_L = "SUBRATING_US_L";
+    field public static final java.lang.String SUBRATING_US_S = "SUBRATING_US_S";
+    field public static final java.lang.String SUBRATING_US_V = "SUBRATING_US_V";
+  }
+
   public final class TvContract {
     method public static final android.net.Uri buildChannelLogoUri(long);
     method public static final android.net.Uri buildChannelLogoUri(android.net.Uri);
@@ -16452,6 +16494,7 @@
     field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
     field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre";
     field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id";
+    field public static final java.lang.String COLUMN_CONTENT_RATING = "content_rating";
     field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
@@ -25450,6 +25493,8 @@
   public static final class Telephony.Mms.Intents {
     field public static final java.lang.String CONTENT_CHANGED_ACTION = "android.intent.action.CONTENT_CHANGED";
     field public static final java.lang.String DELETED_CONTENTS = "deleted_contents";
+    field public static final java.lang.String MMS_DOWNLOAD_ACTION = "android.provider.Telephony.MMS_DOWNLOAD";
+    field public static final java.lang.String MMS_SEND_ACTION = "android.provider.Telephony.MMS_SEND";
   }
 
   public static final class Telephony.Mms.Outbox implements android.provider.Telephony.BaseMmsColumns {
@@ -25553,8 +25598,10 @@
     field public static final java.lang.String SMS_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_CB_RECEIVED";
     field public static final java.lang.String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER";
     field public static final java.lang.String SMS_EMERGENCY_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
+    field public static final java.lang.String SMS_FILTER_ACTION = "android.provider.Telephony.SMS_FILTER";
     field public static final java.lang.String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";
     field public static final java.lang.String SMS_REJECTED_ACTION = "android.provider.Telephony.SMS_REJECTED";
+    field public static final java.lang.String SMS_SEND_ACTION = "android.provider.Telephony.SMS_SEND";
     field public static final java.lang.String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION = "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
     field public static final java.lang.String WAP_PUSH_DELIVER_ACTION = "android.provider.Telephony.WAP_PUSH_DELIVER";
     field public static final java.lang.String WAP_PUSH_RECEIVED_ACTION = "android.provider.Telephony.WAP_PUSH_RECEIVED";
@@ -27980,6 +28027,7 @@
     method public android.telecomm.GatewayInfo getGatewayInfo();
     method public android.net.Uri getHandle();
     method public int getHandlePresentation();
+    method public int getVideoState();
   }
 
   public static abstract class Call.Listener {
@@ -28222,6 +28270,7 @@
     method public java.lang.String getId();
     method public android.telecomm.CallState getState();
     method public android.telecomm.StatusHints getStatusHints();
+    method public int getVideoState();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
@@ -28377,6 +28426,14 @@
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.intent.extra.START_CALL_WITH_VIDEO_STATE";
   }
 
+  public class TelecommManager {
+    method public void clearAccounts(java.lang.String);
+    method public java.util.List<android.telecomm.PhoneAccount> getEnabledPhoneAccounts();
+    method public android.telecomm.PhoneAccountMetadata getPhoneAccountMetadata(android.telecomm.PhoneAccount);
+    method public void registerPhoneAccount(android.telecomm.PhoneAccount, android.telecomm.PhoneAccountMetadata);
+    method public void unregisterPhoneAccount(android.telecomm.PhoneAccount);
+  }
+
   public class VideoCallProfile implements android.os.Parcelable {
     ctor public VideoCallProfile(int, int);
     method public int describeContents();
@@ -28738,9 +28795,13 @@
   public final class SmsManager {
     method public java.util.ArrayList<java.lang.String> divideMessage(java.lang.String);
     method public static android.telephony.SmsManager getDefault();
+    method public void injectSmsPdu(byte[], java.lang.String, android.app.PendingIntent);
     method public void sendDataMessage(java.lang.String, java.lang.String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
     method public void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
     method public void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+    method public void updateMmsDownloadStatus(int, byte[]);
+    method public void updateMmsSendStatus(int, boolean);
+    method public void updateSmsSendStatus(int, boolean);
     field public static final int RESULT_ERROR_GENERIC_FAILURE = 1; // 0x1
     field public static final int RESULT_ERROR_NO_SERVICE = 4; // 0x4
     field public static final int RESULT_ERROR_NULL_PDU = 3; // 0x3
@@ -28811,7 +28872,10 @@
   }
 
   public class TelephonyManager {
+    method public void enableSimplifiedNetworkSettings(boolean);
+    method public void enableSimplifiedNetworkSettings(long, boolean);
     method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
+    method public int getCalculatedPreferredNetworkType();
     method public int getCallState();
     method public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
@@ -28828,19 +28892,30 @@
     method public java.lang.String getNetworkOperatorName();
     method public int getNetworkType();
     method public int getPhoneType();
+    method public int getPreferredNetworkType();
     method public java.lang.String getSimCountryIso();
     method public java.lang.String getSimOperator();
     method public java.lang.String getSimOperatorName();
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
+    method public boolean getSimplifiedNetworkSettingsEnabled();
+    method public boolean getSimplifiedNetworkSettingsEnabled(long);
     method public java.lang.String getSubscriberId();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public int hasCarrierPrivileges();
     method public boolean hasIccCard();
+    method public boolean iccCloseLogicalChannel(int);
+    method public int iccOpenLogicalChannel(java.lang.String);
+    method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
     method public void listen(android.telephony.PhoneStateListener, int);
+    method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
+    method public boolean setCdmaSubscription(int);
+    method public void setLine1NumberForDisplay(java.lang.String, java.lang.String);
+    method public void setLine1NumberForDisplay(long, java.lang.String, java.lang.String);
+    method public boolean setPreferredNetworkType(int);
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
     field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -29363,6 +29438,7 @@
     method public android.content.res.AssetManager getAssets();
     method public java.io.File getCacheDir();
     method public java.lang.ClassLoader getClassLoader();
+    method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
@@ -36159,14 +36235,28 @@
     method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
   }
 
+  public abstract interface WebResourceRequest {
+    method public abstract java.lang.String getMethod();
+    method public abstract java.util.Map<java.lang.String, java.lang.String> getRequestHeaders();
+    method public abstract android.net.Uri getUrl();
+    method public abstract boolean hasUserGestureInsecure();
+    method public abstract boolean isForMainFrame();
+  }
+
   public class WebResourceResponse {
     ctor public WebResourceResponse(java.lang.String, java.lang.String, java.io.InputStream);
+    ctor public WebResourceResponse(java.lang.String, java.lang.String, int, java.lang.String, java.util.Map<java.lang.String, java.lang.String>, java.io.InputStream);
     method public java.io.InputStream getData();
     method public java.lang.String getEncoding();
     method public java.lang.String getMimeType();
+    method public java.lang.String getReasonPhrase();
+    method public java.util.Map<java.lang.String, java.lang.String> getResponseHeaders();
+    method public int getStatusCode();
     method public void setData(java.io.InputStream);
     method public void setEncoding(java.lang.String);
     method public void setMimeType(java.lang.String);
+    method public void setResponseHeaders(java.util.Map<java.lang.String, java.lang.String>);
+    method public void setStatusCodeAndReasonPhrase(int, java.lang.String);
   }
 
   public abstract class WebSettings {
@@ -36490,7 +36580,8 @@
     method public deprecated void onTooManyRedirects(android.webkit.WebView, android.os.Message, android.os.Message);
     method public void onUnhandledInputEvent(android.webkit.WebView, android.view.InputEvent);
     method public deprecated void onUnhandledKeyEvent(android.webkit.WebView, android.view.KeyEvent);
-    method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String);
+    method public deprecated android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String);
+    method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest);
     method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent);
     method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
     field public static final int ERROR_AUTHENTICATION = -4; // 0xfffffffc
@@ -37154,8 +37245,16 @@
     ctor public DatePicker(android.content.Context, android.util.AttributeSet);
     ctor public DatePicker(android.content.Context, android.util.AttributeSet, int);
     ctor public DatePicker(android.content.Context, android.util.AttributeSet, int, int);
+    method public android.content.res.ColorStateList getCalendarTextColor();
     method public android.widget.CalendarView getCalendarView();
     method public boolean getCalendarViewShown();
+    method public int getDateSelectorBackgroundColor();
+    method public int getDateSelectorDayOfMonthTextAppearance();
+    method public int getDateSelectorDayOfWeekBackgroundColor();
+    method public int getDateSelectorDayOfWeekTextAppearance();
+    method public int getDateSelectorMonthTextAppearance();
+    method public int getDateSelectorYearListItemTextAppearance();
+    method public int getDateSelectorYearTextAppearance();
     method public int getDayOfMonth();
     method public long getMaxDate();
     method public long getMinDate();
@@ -37163,7 +37262,15 @@
     method public boolean getSpinnersShown();
     method public int getYear();
     method public void init(int, int, int, android.widget.DatePicker.OnDateChangedListener);
+    method public void setCalendarTextColor(android.content.res.ColorStateList);
     method public void setCalendarViewShown(boolean);
+    method public void setDateSelectorBackgroundColor(int);
+    method public void setDateSelectorDayOfMonthTextAppearance(int);
+    method public void setDateSelectorDayOfWeekBackgroundColor(int);
+    method public void setDateSelectorDayOfWeekTextAppearance(int);
+    method public void setDateSelectorMonthTextAppearance(int);
+    method public void setDateSelectorYearListItemTextAppearance(int);
+    method public void setDateSelectorYearTextAppearance(int);
     method public void setMaxDate(long);
     method public void setMinDate(long);
     method public void setSpinnersShown(boolean);
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 800b925..c771f65 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -114,7 +114,7 @@
             List<IBinder> sessions = mSessionService
                     .getSessions(null, ActivityManager.getCurrentUser());
             for (IBinder session : sessions) {
-                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                MediaController controller = new MediaController(ISessionController.Stub
                         .asInterface(session));
                 if (controller != null && controller.getSessionInfo().getId().equals(id)) {
                     ControllerMonitor monitor = new ControllerMonitor(controller);
@@ -248,7 +248,7 @@
             List<IBinder> sessions = mSessionService
                     .getSessions(null, ActivityManager.getCurrentUser());
             for (IBinder session : sessions) {
-                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                MediaController controller = new MediaController(ISessionController.Stub
                         .asInterface(session));
                 if (controller != null) {
                     MediaSessionInfo info = controller.getSessionInfo();
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index b7f1ff9..d6c17ae 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -998,8 +998,8 @@
 
         final InstallSessionParams params = new InstallSessionParams();
         params.installFlags = PackageManager.INSTALL_ALL_USERS;
-        params.mode = InstallSessionParams.MODE_FULL_INSTALL;
-        params.progressMax = -1;
+        params.setModeFullInstall();
+        params.setProgressMax(-1);
 
         String opt;
         while ((opt = nextOption()) != null) {
@@ -1021,10 +1021,11 @@
             } else if (opt.equals("-d")) {
                 params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
             } else if (opt.equals("-p")) {
-                params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
+                params.setModeInheritExisting();
             } else if (opt.equals("-S")) {
-                params.deltaSize = Long.parseLong(nextOptionData());
-                params.progressMax = (int) params.deltaSize;
+                final long deltaSize = Long.parseLong(nextOptionData());
+                params.setDeltaSize(deltaSize);
+                params.setProgressMax((int) params.deltaSize);
             } else if (opt.equals("--abi")) {
                 params.abiOverride = checkAbiArgument(nextOptionData());
             } else {
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 129e52c..95f83ac 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -356,6 +356,22 @@
     public void setTarget(Object target) {
     }
 
+    // Hide reverse() and canReverse() for now since reverse() only work for simple
+    // cases, like we don't support sequential, neither startDelay.
+    // TODO: make reverse() works for all the Animators.
+    /**
+     * @hide
+     */
+    public boolean canReverse() {
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public void reverse() {
+    }
+
     /**
      * <p>An animation listener receives notifications from an animation.
      * Notifications indicate animation related events, such as the end or the
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 018a2d6..9156eeb 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -124,7 +124,7 @@
     // was set on this AnimatorSet, so it should not be passed down to the children.
     private TimeInterpolator mInterpolator = null;
 
-
+    private boolean mReversible = true;
     /**
      * Sets up this AnimatorSet to play all of the supplied animations at the same time.
      * This is equivalent to calling {@link #play(Animator)} with the first animator in the
@@ -177,6 +177,7 @@
             if (items.length == 1) {
                 play(items[0]);
             } else {
+                mReversible = false;
                 for (int i = 0; i < items.length - 1; ++i) {
                     play(items[i]).before(items[i+1]);
                 }
@@ -196,6 +197,7 @@
             if (items.size() == 1) {
                 play(items.get(0));
             } else {
+                mReversible = false;
                 for (int i = 0; i < items.size() - 1; ++i) {
                     play(items.get(i)).before(items.get(i+1));
                 }
@@ -407,6 +409,9 @@
      */
     @Override
     public void setStartDelay(long startDelay) {
+        if (mStartDelay > 0) {
+            mReversible = false;
+        }
         mStartDelay = startDelay;
     }
 
@@ -512,7 +517,7 @@
                 node.animation.setInterpolator(mInterpolator);
             }
         }
-            // First, sort the nodes (if necessary). This will ensure that sortedNodes
+        // First, sort the nodes (if necessary). This will ensure that sortedNodes
         // contains the animation nodes in the correct order.
         sortNodes();
 
@@ -626,6 +631,7 @@
         anim.mNodeMap = new HashMap<Animator, Node>();
         anim.mNodes = new ArrayList<Node>();
         anim.mSortedNodes = new ArrayList<Node>();
+        anim.mReversible = mReversible;
 
         // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
         // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
@@ -908,6 +914,35 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    public boolean canReverse() {
+        if (!mReversible)  {
+            return false;
+        }
+        // Loop to make sure all the Nodes can reverse.
+        for (Node node : mNodes) {
+            if (!node.animation.canReverse() || node.animation.getStartDelay() > 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void reverse() {
+        if (canReverse()) {
+            for (Node node : mNodes) {
+                node.animation.reverse();
+            }
+        }
+    }
+
+    /**
      * Dependency holds information about the node that some other node is
      * dependent upon and the nature of that dependency.
      *
@@ -1124,6 +1159,7 @@
          * {@link AnimatorSet#play(Animator)} method ends.
          */
         public Builder before(Animator anim) {
+            mReversible = false;
             Node node = mNodeMap.get(anim);
             if (node == null) {
                 node = new Node(anim);
@@ -1144,6 +1180,7 @@
          * {@link AnimatorSet#play(Animator)} method to play.
          */
         public Builder after(Animator anim) {
+            mReversible = false;
             Node node = mNodeMap.get(anim);
             if (node == null) {
                 node = new Node(anim);
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index bf2924c..bdfbde1 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -631,6 +631,9 @@
     public void setObjectValues(Object... values) {
         mValueType = values[0].getClass();
         mKeyframeSet = KeyframeSet.ofObject(values);
+        if (mEvaluator != null) {
+            mKeyframeSet.setEvaluator(mEvaluator);
+        }
     }
 
     /**
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 5338dd0..e3380a9 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1038,6 +1038,7 @@
      * play backwards. This behavior is only set for the current animation; future playing
      * of the animation will use the default behavior of playing forward.
      */
+    @Override
     public void reverse() {
         mPlayingBackwards = !mPlayingBackwards;
         if (mPlayingState == RUNNING) {
@@ -1053,6 +1054,14 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    public boolean canReverse() {
+        return true;
+    }
+
+    /**
      * Called internally to end an animation by removing it from the animations list. Must be
      * called on the UI thread.
      */
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4a70e15..e2b5a84 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -30,7 +30,6 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
@@ -2196,13 +2195,21 @@
             return true;
         }
 
-        case MEDIA_RESOURCES_RELEASED: {
+        case MEDIA_RESOURCES_RELEASED_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
             mediaResourcesReleased(token);
             reply.writeNoException();
             return true;
         }
+
+        case NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            notifyLaunchTaskBehindComplete(token);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -5069,7 +5076,20 @@
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(token);
-        mRemote.transact(MEDIA_RESOURCES_RELEASED, data, reply, IBinder.FLAG_ONEWAY);
+        mRemote.transact(MEDIA_RESOURCES_RELEASED_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
+    public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION, data, reply,
+                IBinder.FLAG_ONEWAY);
         reply.readException();
         data.recycle();
         reply.recycle();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index fafa948..4c02314 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -126,6 +126,8 @@
     public static final int ANIM_SCENE_TRANSITION = 5;
     /** @hide */
     public static final int ANIM_DEFAULT = 6;
+    /** @hide */
+    public static final int ANIM_LAUNCH_TASK_BEHIND = 7;
 
     private String mPackageName;
     private int mAnimationType = ANIM_NONE;
@@ -432,6 +434,27 @@
         return opts;
     }
 
+    /**
+     * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
+     * presented to the user but will instead be only available through the recents task list.
+     * In addition, the new task wil be affiliated with the launching activity's task.
+     * Affiliated tasks are grouped together in the recents task list.
+     *
+     * <p>This behavior is not supported for activities with {@link
+     * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
+     * <code>singleInstance</code> or <code>singleTask</code>.
+     */
+    public static ActivityOptions makeLaunchTaskBehindAnimation() {
+        final ActivityOptions opts = new ActivityOptions();
+        opts.mAnimationType = ANIM_LAUNCH_TASK_BEHIND;
+        return opts;
+    }
+
+    /** @hide */
+    public boolean getLaunchTaskBehind() {
+        return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
+    }
+
     private ActivityOptions() {
     }
 
@@ -647,16 +670,15 @@
         if (mPackageName != null) {
             b.putString(KEY_PACKAGE_NAME, mPackageName);
         }
+        b.putInt(KEY_ANIM_TYPE, mAnimationType);
         switch (mAnimationType) {
             case ANIM_CUSTOM:
-                b.putInt(KEY_ANIM_TYPE, mAnimationType);
                 b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
                 b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
                 b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
                         != null ? mAnimationStartedListener.asBinder() : null);
                 break;
             case ANIM_SCALE_UP:
-                b.putInt(KEY_ANIM_TYPE, mAnimationType);
                 b.putInt(KEY_ANIM_START_X, mStartX);
                 b.putInt(KEY_ANIM_START_Y, mStartY);
                 b.putInt(KEY_ANIM_START_WIDTH, mStartWidth);
@@ -664,7 +686,6 @@
                 break;
             case ANIM_THUMBNAIL_SCALE_UP:
             case ANIM_THUMBNAIL_SCALE_DOWN:
-                b.putInt(KEY_ANIM_TYPE, mAnimationType);
                 b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
                 b.putInt(KEY_ANIM_START_X, mStartX);
                 b.putInt(KEY_ANIM_START_Y, mStartY);
@@ -672,7 +693,6 @@
                         != null ? mAnimationStartedListener.asBinder() : null);
                 break;
             case ANIM_SCENE_TRANSITION:
-                b.putInt(KEY_ANIM_TYPE, mAnimationType);
                 if (mTransitionReceiver != null) {
                     b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
                 }
@@ -683,6 +703,7 @@
                 b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
                 break;
         }
+
         return b;
     }
 
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 0d2af8c..4f556a8 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -95,6 +95,8 @@
      */
     private int mExitTransitionCoordinatorsKey = 1;
 
+    private boolean mIsEnterTriggered;
+
     public ActivityTransitionState() {
     }
 
@@ -142,8 +144,10 @@
     public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
         if (activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)
                 && options != null && mEnterActivityOptions == null
+                && mEnterTransitionCoordinator == null
                 && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
             mEnterActivityOptions = options;
+            mIsEnterTriggered = false;
             if (mEnterActivityOptions.isReturning()) {
                 int result = mEnterActivityOptions.getResultCode();
                 if (result != 0) {
@@ -154,9 +158,10 @@
     }
 
     public void enterReady(Activity activity) {
-        if (mEnterActivityOptions == null) {
+        if (mEnterActivityOptions == null || mIsEnterTriggered) {
             return;
         }
+        mIsEnterTriggered = true;
         mHasExited = false;
         ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
         ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
@@ -259,6 +264,7 @@
             return;
         }
         ActivityOptions activityOptions = new ActivityOptions(options);
+        mEnterTransitionCoordinator = null;
         if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
             int key = activityOptions.getExitCoordinatorKey();
             int index = mExitTransitionCoordinators.indexOfKey(key);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 264553b..6a51371 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -62,6 +62,7 @@
 import android.util.Log;
 import android.view.Display;
 import com.android.internal.util.Preconditions;
+import dalvik.system.VMRuntime;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -251,6 +252,10 @@
         try {
             ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, mContext.getUserId());
             if (ai != null) {
+                // This is a temporary hack. Callers must use
+                // createPackageContext(packageName).getApplicationInfo() to
+                // get the right paths.
+                maybeAdjustApplicationInfo(ai);
                 return ai;
             }
         } catch (RemoteException e) {
@@ -260,6 +265,24 @@
         throw new NameNotFoundException(packageName);
     }
 
+    private static void maybeAdjustApplicationInfo(ApplicationInfo info) {
+        // If we're dealing with a multi-arch application that has both
+        // 32 and 64 bit shared libraries, we might need to choose the secondary
+        // depending on what the current runtime's instruction set is.
+        if (info.primaryCpuAbi != null && info.secondaryCpuAbi != null) {
+            final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();
+            final String secondaryIsa = VMRuntime.getInstructionSet(info.secondaryCpuAbi);
+
+            // If the runtimeIsa is the same as the primary isa, then we do nothing.
+            // Everything will be set up correctly because info.nativeLibraryDir will
+            // correspond to the right ISA.
+            if (runtimeIsa.equals(secondaryIsa)) {
+                info.nativeLibraryDir = info.secondaryNativeLibraryDir;
+            }
+        }
+    }
+
+
     @Override
     public ActivityInfo getActivityInfo(ComponentName className, int flags)
             throws NameNotFoundException {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b9de220..e274f09 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -250,6 +250,8 @@
     private File mNoBackupFilesDir;
     @GuardedBy("mSync")
     private File mCacheDir;
+    @GuardedBy("mSync")
+    private File mCodeCacheDir;
 
     @GuardedBy("mSync")
     private File[] mExternalObbDirs;
@@ -1055,6 +1057,16 @@
     }
 
     @Override
+    public File getCodeCacheDir() {
+        synchronized (mSync) {
+            if (mCodeCacheDir == null) {
+                mCodeCacheDir = new File(getDataDirFile(), "code_cache");
+            }
+            return createFilesDirLocked(mCodeCacheDir);
+        }
+    }
+
+    @Override
     public File getExternalCacheDir() {
         // Operates on primary external storage
         return getExternalCacheDirs()[0];
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 26c2c30..d7fb707 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -21,6 +21,7 @@
 import android.content.DialogInterface.OnClickListener;
 import android.os.Bundle;
 import android.text.format.DateUtils;
+import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.DatePicker;
@@ -44,10 +45,11 @@
     private static final String DAY = "day";
 
     private final DatePicker mDatePicker;
-    private final OnDateSetListener mCallBack;
+    private final OnDateSetListener mDateSetListener;
     private final Calendar mCalendar;
 
     private boolean mTitleNeedsUpdate = true;
+    private boolean mIsCanceled = false;
 
     /**
      * The callback used to indicate the user is done filling in the date.
@@ -79,29 +81,36 @@
         this(context, 0, callBack, year, monthOfYear, dayOfMonth);
     }
 
+    static int resolveDialogTheme(Context context, int resid) {
+        if (resid == 0) {
+            TypedValue outValue = new TypedValue();
+            context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
+            return outValue.resourceId;
+        } else {
+            return resid;
+        }
+    }
+
     /**
      * @param context The context the dialog is to run in.
      * @param theme the theme to apply to this dialog
-     * @param callBack How the parent is notified that the date is set.
+     * @param listener How the parent is notified that the date is set.
      * @param year The initial year of the dialog.
      * @param monthOfYear The initial month of the dialog.
      * @param dayOfMonth The initial day of the dialog.
      */
     public DatePickerDialog(Context context,
             int theme,
-            OnDateSetListener callBack,
+            OnDateSetListener listener,
             int year,
             int monthOfYear,
             int dayOfMonth) {
-        super(context, theme);
+        super(context, resolveDialogTheme(context, theme));
 
-        mCallBack = callBack;
-
+        mDateSetListener = listener;
         mCalendar = Calendar.getInstance();
 
         Context themeContext = getContext();
-        setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
-        setIcon(0);
 
         LayoutInflater inflater =
                 (LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -109,20 +118,53 @@
         setView(view);
         setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
         mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
+
+        // Initialize state
+        mDatePicker.setLegacyMode(false, null);
+        mDatePicker.setShowDoneButton(true);
+        mDatePicker.setDismissCallback(new DatePicker.DatePickerDismissCallback() {
+            @Override
+            public void dismiss(DatePicker view, boolean isCancel, int year, int month, int dayOfMonth) {
+                mIsCanceled = isCancel;
+                if (!isCancel) {
+                    mDateSetListener.onDateSet(view, year, month, dayOfMonth);
+                }
+                DatePickerDialog.this.dismiss();
+            }
+        });
         mDatePicker.init(year, monthOfYear, dayOfMonth, this);
-        updateTitle(year, monthOfYear, dayOfMonth);
     }
 
     public void onClick(DialogInterface dialog, int which) {
         tryNotifyDateSet();
     }
 
+    @Override
+    public void cancel() {
+        mIsCanceled = true;
+        super.cancel();
+    }
+
+    @Override
+    protected void onStop() {
+        tryNotifyDateSet();
+        super.onStop();
+    }
+
     public void onDateChanged(DatePicker view, int year,
             int month, int day) {
         mDatePicker.init(year, month, day, this);
         updateTitle(year, month, day);
     }
 
+    private void tryNotifyDateSet() {
+        if (mDateSetListener != null && !mIsCanceled) {
+            mDatePicker.clearFocus();
+            mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(),
+                    mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
+        }
+    }
+
     /**
      * Gets the {@link DatePicker} contained in this dialog.
      *
@@ -143,20 +185,6 @@
         mDatePicker.updateDate(year, monthOfYear, dayOfMonth);
     }
 
-    private void tryNotifyDateSet() {
-        if (mCallBack != null) {
-            mDatePicker.clearFocus();
-            mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
-                    mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        tryNotifyDateSet();
-        super.onStop();
-    }
-
     private void updateTitle(int year, int month, int day) {
         if (!mDatePicker.getCalendarViewShown()) {
             mCalendar.set(Calendar.YEAR, year);
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 3f3e00c..4f5a098 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -329,7 +329,7 @@
     private Bundle captureExitSharedElementsState() {
         Bundle bundle = new Bundle();
         Rect bounds = new Rect();
-        for (int i = 0; i < mSharedElementNames.size(); i++) {
+        for (int i = 0; i < mSharedElements.size(); i++) {
             String name = mSharedElementNames.get(i);
             Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
             if (sharedElementState != null) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b3a8a8a..c6921a2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -444,6 +444,8 @@
     public boolean isBackgroundMediaPlaying(IBinder token) throws RemoteException;
     public void mediaResourcesReleased(IBinder token) throws RemoteException;
 
+    public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -750,5 +752,6 @@
     int IS_TOP_OF_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+224;
     int SET_MEDIA_PLAYING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+225;
     int IS_BG_MEDIA_PLAYING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+226;
-    int MEDIA_RESOURCES_RELEASED = IBinder.FIRST_CALL_TRANSACTION+227;
+    int MEDIA_RESOURCES_RELEASED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+227;
+    int NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+228;
 }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index a3ffc00..5b92538 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -44,6 +44,9 @@
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     boolean areNotificationsEnabledForPackage(String pkg, int uid);
 
+    void setPackagePriority(String pkg, int uid, int priority);
+    int getPackagePriority(String pkg, int uid);
+
     // TODO: Remove this when callers have been migrated to the equivalent
     // INotificationListener method.
     StatusBarNotification[] getActiveNotifications(String callingPkg);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 065e88d..38614a0 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -44,6 +44,7 @@
 import android.util.SparseArray;
 import android.view.DisplayAdjustments;
 import android.view.Display;
+import dalvik.system.VMRuntime;
 
 import java.io.File;
 import java.io.IOException;
@@ -55,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.Objects;
 
 final class IntentReceiverLeaked extends AndroidRuntimeException {
     public IntentReceiverLeaked(String msg) {
@@ -121,6 +123,8 @@
             CompatibilityInfo compatInfo, ClassLoader baseLoader,
             boolean securityViolation, boolean includeCode) {
         final int myUid = Process.myUid();
+        aInfo = adjustNativeLibraryPaths(aInfo);
+
         mActivityThread = activityThread;
         mApplicationInfo = aInfo;
         mPackageName = aInfo.packageName;
@@ -143,6 +147,27 @@
         mDisplayAdjustments.setCompatibilityInfo(compatInfo);
     }
 
+    private static ApplicationInfo adjustNativeLibraryPaths(ApplicationInfo info) {
+        // If we're dealing with a multi-arch application that has both
+        // 32 and 64 bit shared libraries, we might need to choose the secondary
+        // depending on what the current runtime's instruction set is.
+        if (info.primaryCpuAbi != null && info.secondaryCpuAbi != null) {
+            final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();
+            final String secondaryIsa = VMRuntime.getInstructionSet(info.secondaryCpuAbi);
+
+            // If the runtimeIsa is the same as the primary isa, then we do nothing.
+            // Everything will be set up correctly because info.nativeLibraryDir will
+            // correspond to the right ISA.
+            if (runtimeIsa.equals(secondaryIsa)) {
+                final ApplicationInfo modified = new ApplicationInfo(info);
+                modified.nativeLibraryDir = modified.secondaryNativeLibraryDir;
+                return modified;
+            }
+        }
+
+        return info;
+    }
+
     /**
      * Create information about the system package.
      * Must call {@link #installSystemApplicationInfo} later.
@@ -228,6 +253,22 @@
             }
 
             if (mIncludeCode && !mPackageName.equals("android")) {
+                // Avoid the binder call when the package is the current application package.
+                // The activity manager will perform ensure that dexopt is performed before
+                // spinning up the process.
+                if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
+                    final String isa = VMRuntime.getRuntime().vmInstructionSet();
+                    try {
+                        // TODO: We can probably do away with the isa argument since
+                        // the AM and PM have enough information to figure this out
+                        // themselves. If we do need it, we should match it against the
+                        // list of devices ISAs before sending it down to installd.
+                        ActivityThread.getPackageManager().performDexOptIfNeeded(mPackageName, isa);
+                    } catch (RemoteException re) {
+                        // Ignored.
+                    }
+                }
+
                 final ArrayList<String> zipPaths = new ArrayList<>();
                 final ArrayList<String> libPaths = new ArrayList<>();
 
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index ac74ca1..8a53e08 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -64,6 +64,8 @@
     int mInitialMinute;
     boolean mIs24HourView;
 
+    private boolean mIsCanceled;
+
     /**
      * @param context Parent.
      * @param callBack How parent is notified.
@@ -124,10 +126,13 @@
         mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
             @Override
             public void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute) {
+                mIsCanceled = isCancel;
                 if (!isCancel) {
                     mTimeSetCallback.onTimeSet(view, hourOfDay, minute);
+                    TimePickerDialog.this.dismiss();
+                } else {
+                    TimePickerDialog.this.cancel();
                 }
-                TimePickerDialog.this.dismiss();
             }
         });
         mTimePicker.setIs24HourView(mIs24HourView);
@@ -150,7 +155,7 @@
     }
 
     private void tryNotifyTimeSet() {
-        if (mTimeSetCallback != null) {
+        if (mTimeSetCallback != null && !mIsCanceled) {
             mTimePicker.clearFocus();
             mTimeSetCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
                     mTimePicker.getCurrentMinute());
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7790640..4e7dac0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2243,7 +2243,7 @@
      * application running in the managed profile.
      *
      * <p>The provided {@link Bundle} consists of key-value pairs, where the types of values may be
-     * {@link Boolean}, {@link String}, or {@link String}[]. The recommended format for key strings
+     * boolean, int, String, or String[]. The recommended format for keys
      * is "com.example.packagename/example-setting" to avoid naming conflicts with library
      * components such as {@link android.webkit.WebView}.
      *
@@ -2524,6 +2524,45 @@
     }
 
     /**
+     * Called by profile or device owner to re-enable a system app that was disabled by default
+     * when the managed profile was created. This can 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 can 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
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c27d1cc..d36497e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -141,6 +141,9 @@
     boolean removeUser(in ComponentName who, in UserHandle userHandle);
     boolean switchUser(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();
 
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index ba2930b..28108a0 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -331,6 +331,10 @@
      * its datastore, if appropriate, and close the socket that had been provided in
      * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
      *
+     * <p class="note">If the transport returns TRANSPORT_OK from this method, then the
+     * OS will always provide a matching call to {@link #finishBackup()} even if sending
+     * data via {@link #sendBackupData(int)} failed at some point.
+     *
      * @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
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 97e3fc5..faf8645 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -219,22 +219,6 @@
             "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
 
     /**
-     * Broadcast Action: Indicate BLE Advertising is started.
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_BLUETOOTH_ADVERTISING_STARTED =
-            "android.bluetooth.adapter.action.ADVERTISING_STARTED";
-
-    /**
-     * Broadcast Action: Indicated BLE Advertising is stopped.
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_BLUETOOTH_ADVERTISING_STOPPED =
-            "android.bluetooth.adapter.action.ADVERTISING_STOPPED";
-
-    /**
      * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
      * intents to request the current scan mode. Possible values are:
      * {@link #SCAN_MODE_NONE},
@@ -403,8 +387,6 @@
     private IBluetooth mService;
 
     private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients;
-    private BluetoothAdvScanData mBluetoothAdvScanData = null;
-    private GattCallbackWrapper mAdvertisingGattCallback;
     private final Handler mHandler;  // Handler to post the advertise callback to run on main thread.
     private final Object mLock = new Object();
 
@@ -481,29 +463,6 @@
     }
 
     /**
-     * Returns a {@link BluetoothAdvScanData} object representing advertising data.
-     * Data will be reset when bluetooth service is turned off.
-     * @hide
-     */
-    public BluetoothAdvScanData getAdvScanData() {
-      try {
-          IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
-          if (iGatt == null) {
-              // BLE is not supported
-              Log.e(TAG, "failed to start, iGatt null");
-              return null;
-          }
-          if (mBluetoothAdvScanData == null) {
-              mBluetoothAdvScanData = new BluetoothAdvScanData(iGatt, BluetoothAdvScanData.AD);
-          }
-          return mBluetoothAdvScanData;
-      } catch (RemoteException e) {
-          Log.e(TAG, "failed to get advScanData, error: " + e);
-          return null;
-      }
-    }
-
-    /**
      * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations.
      */
     public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
@@ -520,106 +479,6 @@
     }
 
     /**
-     * Interface for BLE advertising callback.
-     *
-     * @hide
-     */
-    public interface AdvertiseCallback {
-        /**
-         * Callback when advertise starts.
-         * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
-         */
-        void onAdvertiseStart(int status);
-        /**
-         * Callback when advertise stops.
-         * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
-         */
-        void onAdvertiseStop(int status);
-    }
-
-    /**
-     * Start BLE advertising using current {@link BluetoothAdvScanData}.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
-     *
-     * @param callback - {@link AdvertiseCallback}
-     * @return true if BLE advertising succeeds, false otherwise.
-     * @hide
-     */
-    public boolean startAdvertising(final AdvertiseCallback callback) {
-        if (getState() != STATE_ON) return false;
-        try {
-            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
-            if (iGatt == null) {
-                // BLE is not supported.
-                return false;
-            }
-            // Restart/reset advertising packets if advertising is in progress.
-            if (isAdvertising()) {
-                // Invalid advertising callback.
-                if (mAdvertisingGattCallback == null || mAdvertisingGattCallback.mLeHandle == -1) {
-                    Log.e(TAG, "failed to restart advertising, invalid callback");
-                    return false;
-                }
-                iGatt.startAdvertising(mAdvertisingGattCallback.mLeHandle);
-                // Run the callback from main thread.
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        // callback with status success.
-                        callback.onAdvertiseStart(ADVERTISE_CALLBACK_SUCCESS);
-                    }
-                });
-                return true;
-            }
-            UUID uuid = UUID.randomUUID();
-            GattCallbackWrapper wrapper =
-                new GattCallbackWrapper(this, null, null, callback);
-            iGatt.registerClient(new ParcelUuid(uuid), wrapper);
-            if (!wrapper.advertiseStarted()) {
-                return false;
-            }
-            synchronized (mLock) {
-                mAdvertisingGattCallback = wrapper;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
-        }
-    }
-
-    /**
-     * Stop BLE advertising. The callback has to be the same one used for start advertising.
-     *
-     * @param callback - {@link AdvertiseCallback}
-     * @return true if BLE advertising stops, false otherwise.
-     * @hide
-     */
-    public boolean stopAdvertising(AdvertiseCallback callback) {
-        try {
-            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
-            if (iGatt == null) {
-                // BLE is not supported
-                return false;
-            }
-            if (mAdvertisingGattCallback == null) {
-                // no callback.
-                return false;
-            }
-            // Make sure same callback is used for start and stop advertising.
-            if (callback != mAdvertisingGattCallback.mAdvertiseCallback) {
-                Log.e(TAG, "must use the same callback for star/stop advertising");
-                return false;
-            }
-            mAdvertisingGattCallback.stopAdvertising();
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
-        }
-    }
-
-    /**
      * Return true if Bluetooth is currently enabled and ready for use.
      * <p>Equivalent to:
      * <code>getBluetoothState() == STATE_ON</code>
@@ -1076,23 +935,6 @@
     }
 
     /**
-     * Returns whether BLE is currently advertising.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
-     *
-     * @hide
-     */
-    public boolean isAdvertising() {
-        if (getState() != STATE_ON) return false;
-        try {
-            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
-            return iGatt.isAdvertising();
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-        }
-        return false;
-    }
-
-    /**
      * Return the set of {@link BluetoothDevice} objects that are bonded
      * (paired) to the local adapter.
      * <p>If Bluetooth state is not {@link #STATE_ON}, this API
@@ -1537,8 +1379,6 @@
                 if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
                 synchronized (mManagerCallback) {
                     mService = null;
-                    // Reset bluetooth adv scan data when Gatt service is down.
-                    mBluetoothAdvScanData = null;
                     for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
                         try {
                             if (cb != null) {
@@ -1822,7 +1662,6 @@
         private static final int LE_CALLBACK_REG_TIMEOUT = 2000;
         private static final int LE_CALLBACK_REG_WAIT_COUNT = 5;
 
-        private final AdvertiseCallback mAdvertiseCallback;
         private final LeScanCallback mLeScanCb;
 
         // mLeHandle 0: not registered
@@ -1838,27 +1677,12 @@
             mLeScanCb = leScanCb;
             mScanFilter = uuid;
             mLeHandle = 0;
-            mAdvertiseCallback = null;
-        }
-
-        public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter, LeScanCallback leScanCb,
-            UUID[] uuid, AdvertiseCallback callback) {
-          mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter);
-          mLeScanCb = leScanCb;
-          mScanFilter = uuid;
-          mLeHandle = 0;
-          mAdvertiseCallback = callback;
         }
 
         public boolean scanStarted() {
             return waitForRegisteration(LE_CALLBACK_REG_WAIT_COUNT);
         }
 
-        public boolean advertiseStarted() {
-            // Wait for registeration callback.
-            return waitForRegisteration(1);
-        }
-
         private boolean waitForRegisteration(int maxWaitCount) {
             boolean started = false;
             synchronized(this) {
@@ -1878,27 +1702,6 @@
             return started;
         }
 
-        public void stopAdvertising() {
-            synchronized (this) {
-                if (mLeHandle <= 0) {
-                    Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
-                    return;
-                }
-                BluetoothAdapter adapter = mBluetoothAdapter.get();
-                if (adapter != null) {
-                    try {
-                        IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt();
-                        iGatt.stopAdvertising();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to stop advertising" + e);
-                    }
-                } else {
-                    Log.e(TAG, "stopAdvertising, BluetoothAdapter is null");
-                }
-                notifyAll();
-            }
-        }
-
         public void stopLeScan() {
             synchronized(this) {
                 if (mLeHandle <= 0) {
@@ -1940,18 +1743,14 @@
                         BluetoothAdapter adapter = mBluetoothAdapter.get();
                         if (adapter != null) {
                             iGatt = adapter.getBluetoothManager().getBluetoothGatt();
-                            if (mAdvertiseCallback != null) {
-                                iGatt.startAdvertising(mLeHandle);
+                            if (mScanFilter == null) {
+                                iGatt.startScan(mLeHandle, false);
                             } else {
-                              if (mScanFilter == null) {
-                                  iGatt.startScan(mLeHandle, false);
-                              } else {
-                                  ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
-                                  for(int i = 0; i != uuids.length; ++i) {
-                                      uuids[i] = new ParcelUuid(mScanFilter[i]);
-                                  }
-                                  iGatt.startScanWithUuids(mLeHandle, false, uuids);
-                              }
+                                ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
+                                for(int i = 0; i != uuids.length; ++i) {
+                                    uuids[i] = new ParcelUuid(mScanFilter[i]);
+                                }
+                                iGatt.startScanWithUuids(mLeHandle, false, uuids);
                             }
                         } else {
                             Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
@@ -2080,44 +1879,6 @@
         }
 
         public void onAdvertiseStateChange(int advertiseState, int status) {
-            Log.d(TAG, "on advertise call back, state: " + advertiseState + " status: " + status);
-            if (advertiseState == STATE_ADVERTISE_STARTED) {
-                if (status == ADVERTISE_CALLBACK_SUCCESS) {
-                    mAdvertiseCallback.onAdvertiseStart(status);
-                } else {
-                    // If status is unsuccessful and advertise state is started, it means stop
-                    // advertising fails.
-                    mAdvertiseCallback.onAdvertiseStop(status);
-                }
-            } else {
-                synchronized (this) {
-                    if (status == ADVERTISE_CALLBACK_SUCCESS) {
-                        BluetoothAdapter adapter = mBluetoothAdapter.get();
-                        if (adapter != null) {
-                            try {
-                                IBluetoothGatt iGatt =
-                                        adapter.getBluetoothManager().getBluetoothGatt();
-                                Log.d(TAG, "unregistering client " + mLeHandle);
-                                iGatt.unregisterClient(mLeHandle);
-                                // Reset advertise app handle.
-                                mLeHandle = -1;
-                                adapter.mAdvertisingGattCallback = null;
-                            } catch (RemoteException e) {
-                                Log.e(TAG, "Failed to unregister client" + e);
-                            }
-                        } else {
-                            Log.e(TAG, "cannot unregister client, BluetoothAdapter is null");
-                        }
-                    }
-                }
-                if (status == ADVERTISE_CALLBACK_SUCCESS) {
-                    mAdvertiseCallback.onAdvertiseStop(status);
-                } else{
-                    // If status is unsuccesful and advertise state is stopped, it means start
-                    // advertising fails.
-                    mAdvertiseCallback.onAdvertiseStart(status);
-                }
-            }
         }
 
         @Override
diff --git a/core/java/android/bluetooth/BluetoothAdvScanData.java b/core/java/android/bluetooth/BluetoothAdvScanData.java
deleted file mode 100644
index df2c256..0000000
--- a/core/java/android/bluetooth/BluetoothAdvScanData.java
+++ /dev/null
@@ -1,152 +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.bluetooth;
-import android.os.ParcelUuid;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * This class provides the public APIs to set advertising and scan response data when BLE device
- * operates in peripheral mode. <br>
- * The exact format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11
- * @hide
- */
-public final class BluetoothAdvScanData {
-
-  /**
-   * Available data types of {@link BluetoothAdvScanData}.
-   */
-  public static final int AD = 0;  // Advertising Data
-  public static final int SCAN_RESPONSE = 1;  // Scan Response
-  public static final int EIR = 2;  // Extended Inquiry Response
-
-  private static final String TAG = "BluetoothAdvScanData";
-
-  /**
-   * Data type of BluetoothAdvScanData.
-   */
-  private final int mDataType;
-  /**
-   * Bluetooth Gatt Service.
-   */
-  private IBluetoothGatt mBluetoothGatt;
-
-  /**
-   * @param mBluetoothGatt
-   * @param dataType
-   */
-  public BluetoothAdvScanData(IBluetoothGatt mBluetoothGatt, int dataType) {
-    this.mBluetoothGatt = mBluetoothGatt;
-    this.mDataType = dataType;
-  }
-
-  /**
-   * @return advertising data type.
-   */
-  public int getDataType() {
-    return mDataType;
-  }
-
-  /**
-   * Set manufactureCode and manufactureData.
-   * Returns true if manufacturer data is set, false if there is no enough room to set
-   * manufacturer data or the data is already set.
-   * @param manufacturerCode - unique identifier for the manufacturer
-   * @param manufacturerData - data associated with the specific manufacturer.
-   */
-  public boolean setManufacturerData(int manufacturerCode, byte[] manufacturerData) {
-    if (mDataType != AD) return false;
-    try {
-      return mBluetoothGatt.setAdvManufacturerCodeAndData(manufacturerCode, manufacturerData);
-    } catch (RemoteException e) {
-      Log.e(TAG, "Unable to set manufacturer id and data.", e);
-      return false;
-    }
-  }
-
-  /**
-   * Set service data.  Note the service data can only be set when the data type is {@code AD};
-   * @param serviceData
-   */
-  public boolean setServiceData(byte[] serviceData) {
-
-    if (mDataType != AD) return false;
-    if (serviceData == null) return false;
-    try {
-      return mBluetoothGatt.setAdvServiceData(serviceData);
-    } catch (RemoteException e) {
-      Log.e(TAG, "Unable to set service data.", e);
-      return false;
-    }
-  }
-
-  /**
-   * Returns an immutable list of service uuids that will be advertised.
-   */
-  public List<ParcelUuid> getServiceUuids() {
-    try {
-      return Collections.unmodifiableList(mBluetoothGatt.getAdvServiceUuids());
-    } catch (RemoteException e) {
-      Log.e(TAG, "Unable to get service uuids.", e);
-      return null;
-    }
-  }
-
-  /**
-   * Returns manufacturer data.
-   */
-  public byte[] getManufacturerData() {
-    if (mBluetoothGatt == null) return null;
-    try {
-      return mBluetoothGatt.getAdvManufacturerData();
-    } catch (RemoteException e) {
-      Log.e(TAG, "Unable to get manufacturer data.", e);
-      return null;
-    }
-  }
-
-  /**
-   * Returns service data.
-   */
-  public byte[] getServiceData() {
-    if (mBluetoothGatt == null) return null;
-    try {
-      return mBluetoothGatt.getAdvServiceData();
-    } catch (RemoteException e) {
-      Log.e(TAG, "Unable to get service data.", e);
-      return null;
-    }
-  }
-
-  /**
-   * Remove manufacturer data based on given manufacturer code.
-   * @param manufacturerCode
-   */
-  public void removeManufacturerCodeAndData(int manufacturerCode) {
-    if (mBluetoothGatt != null) {
-      try {
-        mBluetoothGatt.removeAdvManufacturerCodeAndData(manufacturerCode);
-      } catch (RemoteException e) {
-        Log.e(TAG, "Unable to remove manufacturer : " + manufacturerCode, e);
-      }
-    }
-  }
-}
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 0f0eee6..6d4b9cd 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -48,15 +48,6 @@
     void unregisterClient(in int clientIf);
     void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport);
     void clientDisconnect(in int clientIf, in String address);
-    void startAdvertising(in int appIf);
-    void stopAdvertising();
-    boolean setAdvServiceData(in byte[] serviceData);
-    byte[] getAdvServiceData();
-    boolean setAdvManufacturerCodeAndData(int manufactureCode, in byte[] manufacturerData);
-    byte[] getAdvManufacturerData();
-    List<ParcelUuid> getAdvServiceUuids();
-    void removeAdvManufacturerCodeAndData(int manufacturerCode);
-    boolean isAdvertising();
     void refreshDevice(in int clientIf, in String address);
     void discoverServices(in int clientIf, in String address);
     void readCharacteristic(in int clientIf, in String address, in int srvcType,
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index af218eb..18e3f54 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -64,7 +64,6 @@
                              in int charInstId, in ParcelUuid charUuid,
                              in byte[] value);
     void onReadRemoteRssi(in String address, in int rssi, 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/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index e232512..af79fcc 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -332,11 +332,6 @@
         }
 
         @Override
-        public void onAdvertiseStateChange(int advertiseState, int status) {
-            // no op
-        }
-
-        @Override
         public void onMultiAdvertiseCallback(int status) {
             // TODO: This logic needs to be re-visited to account
             //       for whether the scan has actually been started
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 7e87edc..220aa77 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -409,11 +409,6 @@
         }
 
         @Override
-        public void onAdvertiseStateChange(int advertiseState, int status) {
-            // no op
-        }
-
-        @Override
         public void onMultiAdvertiseCallback(int status) {
             // no op
         }
diff --git a/core/java/android/content/AbstractRestrictionsProvider.java b/core/java/android/content/AbstractRestrictionsProvider.java
index 1119478..3272970 100644
--- a/core/java/android/content/AbstractRestrictionsProvider.java
+++ b/core/java/android/content/AbstractRestrictionsProvider.java
@@ -16,78 +16,65 @@
 
 package android.content;
 
-import android.app.Service;
 import android.app.admin.DevicePolicyManager;
 import android.os.Bundle;
 import android.os.IBinder;
 
 /**
- * Abstract implementation of a Restrictions Provider Service. To implement a Restrictions Provider,
- * extend from this class and implement the abstract methods. Export this service in the
- * manifest. A profile owner device admin can then register this component as a Restrictions
- * Provider using {@link DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)}.
+ * Abstract implementation of a Restrictions Provider BroadcastReceiver. To implement a
+ * Restrictions Provider, extend from this class and implement the abstract methods.
+ * Export this receiver in the manifest. A profile owner device admin can then register this
+ * component as a Restrictions Provider using
+ * {@link DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)}.
  * <p>
  * The function of a Restrictions Provider is to transport permission requests from apps on this
  * device to an administrator (most likely on a remote device or computer) and deliver back
  * responses. The response should be sent back to the app via
  * {@link RestrictionsManager#notifyPermissionResponse(String, Bundle)}.
- * <p>
- * Apps can also query previously received responses using
- * {@link #getPermissionResponse(String, String)}. The period for which previously received
- * responses are available is left to the implementation of the Restrictions Provider.
+ *
+ * @see RestrictionsManager
  */
-public abstract class AbstractRestrictionsProvider extends Service {
+public abstract class AbstractRestrictionsProvider extends BroadcastReceiver {
 
     private static final String TAG = "AbstractRestrictionsProvider";
 
-    @Override
-    public final IBinder onBind(Intent intent) {
-        return new RestrictionsProviderWrapper().asBinder();
-    }
-
-    /**
-     * Checks to see if there is a response for a prior request and returns the response bundle if
-     * it exists. If there is no response yet or if the request is not known, the returned bundle
-     * should contain the response code in {@link RestrictionsManager#RESPONSE_KEY_RESULT}.
-     *
-     * @param packageName the application that is requesting a permission response.
-     * @param requestId the id of the request for which the response is needed.
-     * @return a bundle containing at a minimum the result of the request. It could contain other
-     * optional information such as error codes and cookies.
-     *
-     * @see RestrictionsManager#RESPONSE_KEY_RESULT
-     */
-    public abstract Bundle getPermissionResponse(String packageName, String requestId);
-
     /**
      * An asynchronous permission request made by an application for an operation that requires
      * authorization by a local or remote administrator other than the user. The Restrictions
-     * Provider must transfer the request to the administrator and deliver back a response, when
+     * Provider should transfer the request to the administrator and deliver back a response, when
      * available. The calling application is aware that the response could take an indefinite
      * amount of time.
+     * <p>
+     * If the request bundle contains the key {@link RestrictionsManager#REQUEST_KEY_NEW_REQUEST},
+     * then a new request must be sent. Otherwise the provider can look up any previous response
+     * to the same requestId and return the cached response.
      *
      * @param packageName the application requesting permission.
      * @param requestType the type of request, which determines the content and presentation of
      * the request data.
      * @param request the request data bundle containing at a minimum a request id.
      *
-     * @see RestrictionsManager#REQUEST_TYPE_QUESTION
+     * @see RestrictionsManager#REQUEST_TYPE_APPROVAL
      * @see RestrictionsManager#REQUEST_TYPE_LOCAL_APPROVAL
      * @see RestrictionsManager#REQUEST_KEY_ID
      */
-    public abstract void requestPermission(String packageName, String requestType, Bundle request);
+    public abstract void requestPermission(Context context,
+            String packageName, String requestType, Bundle request);
 
-    private class RestrictionsProviderWrapper extends IRestrictionsProvider.Stub {
+    /**
+     * Intercept standard Restrictions Provider broadcasts.  Implementations
+     * should not override this method; it is better to implement the
+     * convenience callbacks for each action.
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
 
-        @Override
-        public Bundle getPermissionResponse(String packageName, String requestId) {
-            return AbstractRestrictionsProvider.this
-                    .getPermissionResponse(packageName, requestId);
-        }
-
-        @Override
-        public void requestPermission(String packageName, String templateId, Bundle request) {
-            AbstractRestrictionsProvider.this.requestPermission(packageName, templateId, request);
+        if (RestrictionsManager.ACTION_REQUEST_PERMISSION.equals(action)) {
+            String packageName = intent.getStringExtra(RestrictionsManager.EXTRA_PACKAGE_NAME);
+            String requestType = intent.getStringExtra(RestrictionsManager.EXTRA_REQUEST_TYPE);
+            Bundle request = intent.getBundleExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE);
+            requestPermission(context, packageName, requestType, request);
         }
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a52cbdd..1569b9f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -867,6 +867,22 @@
     public abstract File getCacheDir();
 
     /**
+     * Returns the absolute path to the application specific cache directory on
+     * the filesystem designed for storing cached code. The system will delete
+     * any files stored in this location both when your specific application is
+     * upgraded, and when the entire platform is upgraded.
+     * <p>
+     * This location is optimal for storing compiled or optimized code generated
+     * by your application at runtime.
+     * <p>
+     * Apps require no extra permissions to read or write to the returned path,
+     * since this path lives in their private storage.
+     *
+     * @return The path of the directory holding application code cache files.
+     */
+    public abstract File getCodeCacheDir();
+
+    /**
      * Returns the absolute path to the directory on the primary external filesystem
      * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
      * Environment.getExternalStorageDirectory()} where the application can
@@ -2523,7 +2539,6 @@
      *
      * @see #getSystemService
      * @see android.telecomm.TelecommManager
-     * @hide
      */
     public static final String TELECOMM_SERVICE = "telecomm";
 
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 13eed07..4e1c4a7 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -232,6 +232,11 @@
     }
 
     @Override
+    public File getCodeCacheDir() {
+        return mBase.getCodeCacheDir();
+    }
+
+    @Override
     public File getExternalCacheDir() {
         return mBase.getExternalCacheDir();
     }
diff --git a/core/java/android/content/IPermissionResponseCallback.aidl b/core/java/android/content/IPermissionResponseCallback.aidl
deleted file mode 100644
index 8309768..0000000
--- a/core/java/android/content/IPermissionResponseCallback.aidl
+++ /dev/null
@@ -1,30 +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.content;
-
-import android.os.Bundle;
-
-/**
- * Callback for permission response queries.
- *
- * @hide
- */
- interface IPermissionResponseCallback {
-
-    void onResponse(in Bundle response);
-
-}
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
index 49eb65b..b1c0a3a 100644
--- a/core/java/android/content/IRestrictionsManager.aidl
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -17,7 +17,6 @@
 package android.content;
 
 import android.os.Bundle;
-import android.content.IPermissionResponseCallback;
 
 /**
  * Interface used by the RestrictionsManager
@@ -28,6 +27,4 @@
     boolean hasRestrictionsProvider();
     void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
     void notifyPermissionResponse(in String packageName, in Bundle response);
-    void getPermissionResponse(in String packageName, in String requestId,
-            in IPermissionResponseCallback callback);
 }
diff --git a/core/java/android/content/IRestrictionsProvider.aidl b/core/java/android/content/IRestrictionsProvider.aidl
deleted file mode 100644
index 4506b72..0000000
--- a/core/java/android/content/IRestrictionsProvider.aidl
+++ /dev/null
@@ -1,31 +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.content;
-
-import android.os.Bundle;
-
-/**
- * Interface to a restrictions provider service component.
- *
- * @hide
- */
- interface IRestrictionsProvider {
-
-    void requestPermission(in String packageName, in String requestType, in Bundle requestBundle);
-    Bundle getPermissionResponse(in String packageName, in String requestId);
-
-}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4153a02..5bf8a97 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3809,17 +3809,6 @@
      * {@link android.R.styleable#AndroidManifestActivity_autoRemoveFromRecents}.
      */
     public static final int FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS = 0x00002000;
-    /**
-     * If set along with FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
-     * presented to the user but will instead be only available through the recents task list.
-     * In addition, the new task wil be affiliated with the launching activity's task.
-     * Affiliated tasks are grouped together in the recents task list.
-     *
-     * <p>This behavior is not supported for activities with {@link
-     * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
-     * <code>singleInstance</code> or <code>singleTask</code>.
-     */
-    public static final int FLAG_ACTIVITY_LAUNCH_BEHIND = 0x00001000;
 
     /**
      * If set, when sending a broadcast only registered receivers will be
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 62f88a9..5341ea8 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -79,6 +79,13 @@
      */
     public static final int TYPE_INTEGER = 5;
 
+    /**
+     * A type of restriction. Use this for storing a string value.
+     * @see #setSelectedString
+     * @see #getSelectedString
+     */
+    public static final int TYPE_STRING = 6;
+
     /** The type of restriction. */
     private int mType;
 
@@ -107,6 +114,17 @@
     private String[] mCurrentValues;
 
     /**
+     * Constructor for specifying the type and key, with no initial value;
+     *
+     * @param type the restriction type.
+     * @param key the unique key for this restriction
+     */
+    public RestrictionEntry(int type, String key) {
+        mType = type;
+        mKey = key;
+    }
+
+    /**
      * Constructor for {@link #TYPE_CHOICE} type.
      * @param key the unique key for this restriction
      * @param selectedString the current value
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 5ef2dbc..5ae10cfc 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -17,11 +17,24 @@
 package android.content;
 
 import android.app.admin.DevicePolicyManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Xml;
 
-import java.util.Collections;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -32,50 +45,77 @@
  * <p>
  * Apps can expose a set of restrictions via an XML file specified in the manifest.
  * <p>
- * If the user has an active restrictions provider, dynamic requests can be made in
+ * 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 request types.
  * <p>
  * The RestrictionsManager forwards the dynamic requests to the active
- * restrictions provider. The restrictions provider can respond back to requests by calling
+ * 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>
- * Prior responses to requests can also be queried through calls to
- * {@link #getPermissionResponse(String, PermissionResponseCallback)}, if the provider
- * saves old responses.
- * <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 list of available restrictions from the apk.
  * <p>
  * The syntax of the XML format is as follows:
  * <pre>
- * &lt;restrictions&gt;
+ * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+ * &lt;restrictions xmlns:android="http://schemas.android.com/apk/res/android" &gt;
  *     &lt;restriction
- *         android:key="&lt;key&gt;"
- *         android:restrictionType="boolean|string|integer|multi-select|null"
- *         ... /&gt;
+ *         android:key="string"
+ *         android:title="string resource"
+ *         android:restrictionType=["bool" | "string" | "integer"
+ *                                         | "choice" | "multi-select" | "hidden"]
+ *         android:description="string resource"
+ *         android:entries="string-array resource"
+ *         android:entryValues="string-array resource"
+ *         android:defaultValue="reference"
+ *         /&gt;
  *     &lt;restriction ... /&gt;
+ *     ...
  * &lt;/restrictions&gt;
  * </pre>
  * <p>
  * The attributes for each restriction depend on the restriction type.
+ * <p>
+ * <ul>
+ * <li><code>key</code>, <code>title</code> and <code>restrictionType</code> are mandatory.</li>
+ * <li><code>entries</code> and <code>entryValues</code> are required if <code>restrictionType
+ * </code> is <code>choice</code> or <code>multi-select</code>.</li>
+ * <li><code>defaultValue</code> is optional and its type depends on the
+ * <code>restrictionType</code></li>
+ * <li><code>hidden</code> type must have a <code>defaultValue</code> and will
+ * not be shown to the administrator. It can be used to pass along data that cannot be modified,
+ * such as a version code.</li>
+ * <li><code>description</code> is meant to describe the restriction in more detail to the
+ * administrator controlling the values, if the title is not sufficient.</li>
+ * </ul>
+ * <p>
+ * In your manifest's <code>application</code> section, add the meta-data tag to point to
+ * the restrictions XML file as shown below:
+ * <pre>
+ * &lt;application ... &gt;
+ *     &lt;meta-data android:name="android.content.APP_RESTRICTIONS"
+ *                   android:resource="@xml/app_restrictions" /&gt;
+ *     ...
+ * &lt;/application&gt;
+ * </pre>
  *
  * @see RestrictionEntry
  * @see AbstractRestrictionsProvider
+ * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
+ * @see DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle)
  */
 public class RestrictionsManager {
 
     private static final String TAG = "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
+     * Broadcast intent delivered when a response is received for a permission request. The
+     * application should not interrupt the user by coming to the foreground if it isn't
      * currently in the foreground. It can either post a notification informing
      * the user of the response or wait until the next time the user launches the app.
      * <p>
@@ -89,9 +129,32 @@
             "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
 
     /**
+     * Broadcast intent sent to the Restrictions Provider to handle a permission request from
+     * an app. It will have the following extras: {@link #EXTRA_PACKAGE_NAME},
+     * {@link #EXTRA_REQUEST_TYPE} and {@link #EXTRA_REQUEST_BUNDLE}. The Restrictions Provider
+     * will handle the request and respond back to the RestrictionsManager, when a response is
+     * available, by calling {@link #notifyPermissionResponse}.
+     * <p>
+     * The BroadcastReceiver must require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN}
+     * permission to ensure that only the system can send the broadcast.
+     */
+    public static final String ACTION_REQUEST_PERMISSION =
+            "android.content.action.REQUEST_PERMISSION";
+
+    /**
      * The package name of the application making the request.
      */
-    public static final String EXTRA_PACKAGE_NAME = "package_name";
+    public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+
+    /**
+     * The request type passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+     */
+    public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
+
+    /**
+     * The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+     */
+    public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
 
     /**
      * Contains a response from the administrator for specific request.
@@ -101,7 +164,7 @@
      * <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li>
      * </ul>
      */
-    public static final String EXTRA_RESPONSE_BUNDLE = "response";
+    public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
 
     /**
      * Request type for a simple question, with a possible title and icon.
@@ -113,7 +176,7 @@
      * {@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_TYPE_QUESTION = "android.request.type.question";
+    public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
 
     /**
      * Request type for a local password challenge. This is a way for an app to ask
@@ -204,26 +267,19 @@
     public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
 
     /**
-     * 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.
+     * Key for issuing a new request, contained in the request bundle. If this is set to true,
+     * the Restrictions Provider must make a new request. If it is false or not specified, then
+     * the Restrictions Provider can return a cached response that has the same requestId, if
+     * available. If there's no cached response, it will issue a new one to the administrator.
      * <p>
-     * Type: String
+     * Type: boolean
      */
-    public static final String REQUEST_KEY_REQUESTOR_NAME = "android.request.requestor";
+    public static final String REQUEST_KEY_NEW_REQUEST = "android.request.new_request";
 
     /**
-     * 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.request.device";
-
-    /**
-     * Key for the response in the response bundle sent to the application, for a permission
-     * request.
+     * Key for the response result in the response bundle sent to the application, for a permission
+     * request. It indicates the status of the request. In some cases an additional message might
+     * be available in {@link #RESPONSE_KEY_MESSAGE}, to be displayed to the user.
      * <p>
      * Type: int
      * <p>
@@ -249,8 +305,8 @@
     public static final int RESULT_NO_RESPONSE = 3;
 
     /**
-     * Response result value indicating that the request is unknown, when returned through a
-     * call to #getPendingResponse
+     * Response result value indicating that the request is unknown, when it's not a new
+     * request.
      */
     public static final int RESULT_UNKNOWN_REQUEST = 4;
 
@@ -258,7 +314,7 @@
      * Response result value indicating an error condition. Additional error code might be available
      * in the response bundle, for the key {@link #RESPONSE_KEY_ERROR_CODE}. There might also be
      * an associated error message in the response bundle, for the key
-     * {@link #RESPONSE_KEY_ERROR_MESSAGE}.
+     * {@link #RESPONSE_KEY_MESSAGE}.
      */
     public static final int RESULT_ERROR = 5;
 
@@ -294,11 +350,11 @@
     public static final String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
 
     /**
-     * Key for the optional error message in the response bundle sent to the application.
+     * Key for the optional message in the response bundle sent to the application.
      * <p>
      * Type: String
      */
-    public static final String RESPONSE_KEY_ERROR_MESSAGE = "android.response.errormsg";
+    public static final String RESPONSE_KEY_MESSAGE = "android.response.msg";
 
     /**
      * Key for the optional timestamp of when the administrator responded to the permission
@@ -308,23 +364,19 @@
      */
     public static final String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
 
+    /**
+     * Name of the meta-data entry in the manifest that points to the XML file containing the
+     * application's available restrictions.
+     * @see #getManifestRestrictions(String)
+     */
+    public static final String META_DATA_APP_RESTRICTIONS = "android.content.APP_RESTRICTIONS";
+
+    private static final String TAG_RESTRICTION = "restriction";
+
     private final Context mContext;
     private final IRestrictionsManager mService;
 
     /**
-     * Callback object for returning a response for a request.
-     *
-     * @see #getPermissionResponse
-     */
-    public static abstract class PermissionResponseCallback {
-        /**
-         * Contains the response
-         * @param response
-         */
-        public abstract void onResponse(Bundle response);
-    }
-
-    /**
      * @hide
      */
     public RestrictionsManager(Context context, IRestrictionsManager service) {
@@ -350,11 +402,10 @@
     }
 
     /**
-     * Called by an application to check if there is an active restrictions provider. If
-     * there isn't, {@link #getPermissionResponse(String, PermissionResponseCallback)}
-     * and {@link #requestPermission(String, Bundle)} are not available.
+     * Called by an application to check if there is an active Restrictions Provider. If
+     * there isn't, {@link #requestPermission(String, Bundle)} is not available.
      *
-     * @return whether there is an active restrictions provider.
+     * @return whether there is an active Restrictions Provider.
      */
     public boolean hasRestrictionsProvider() {
         try {
@@ -374,13 +425,24 @@
      *
      * @param requestType The type of request. The type could be one of the
      * predefined types specified here or a custom type that the specific
-     * restrictions provider might understand. For custom types, the type name should be
+     * Restrictions Provider might understand. For custom types, the type name should be
      * namespaced to avoid collisions with predefined types and types specified by
-     * other restrictions providers.
+     * other Restrictions Providers.
      * @param request A Bundle containing the data corresponding to the specified request
      * type. The keys for the data in the bundle depend on the request type.
+     *
+     * @throws IllegalArgumentException if any of the required parameters are missing.
      */
     public void requestPermission(String requestType, Bundle request) {
+        if (requestType == null) {
+            throw new NullPointerException("requestType cannot be null");
+        }
+        if (request == null) {
+            throw new NullPointerException("request cannot be null");
+        }
+        if (!request.containsKey(REQUEST_KEY_ID)) {
+            throw new IllegalArgumentException("REQUEST_KEY_ID must be specified");
+        }
         try {
             if (mService != null) {
                 mService.requestPermission(mContext.getPackageName(), requestType, request);
@@ -391,41 +453,27 @@
     }
 
     /**
-     * Called by an application to query for any available response from the restrictions provider
-     * for the given requestId. The call returns immediately and the response will be returned
-     * via the provided callback. This does not initiate a new request and does not wait
-     * for a response to be received. It merely returns any previously received response
-     * or indicates if there was no available response. If there are multiple responses
-     * available for the same request ID, the most recent one is returned.
+     * Called by the Restrictions Provider to deliver a response to an application.
      *
-     * @param requestId The ID of the original request made via
-     * {@link #requestPermission(String, Bundle)}. It's possible to also query for responses
-     * to requests made on a different device with the same requestId, if the Restrictions
-     * Provider happens to sync responses across devices with the same account managed by the
-     * restrictions provider.
-     * @param callback The response is returned via the callback object. Cannot be null.
-     */
-    public void getPermissionResponse(String requestId, PermissionResponseCallback callback) {
-        if (requestId == null || callback == null) {
-            throw new NullPointerException("requestId or callback cannot be null");
-        }
-        try {
-            if (mService != null) {
-                mService.getPermissionResponse(mContext.getPackageName(), requestId,
-                        new PermissionResponseCallbackWrapper(callback));
-            }
-        } catch (RemoteException re) {
-            Log.w(TAG, "Couldn't reach service");
-        }
-    }
-
-    /**
-     * Called by the restrictions provider to deliver a response to an application.
-     *
-     * @param packageName the application to deliver the response to.
+     * @param packageName the application to deliver the response to. Cannot be null.
      * @param response the Bundle containing the response status, request ID and other information.
+     *                 Cannot be null.
+     *
+     * @throws IllegalArgumentException if any of the required parameters are missing.
      */
     public void notifyPermissionResponse(String packageName, Bundle response) {
+        if (packageName == null) {
+            throw new NullPointerException("packageName cannot be null");
+        }
+        if (response == null) {
+            throw new NullPointerException("request cannot be null");
+        }
+        if (!response.containsKey(REQUEST_KEY_ID)) {
+            throw new IllegalArgumentException("REQUEST_KEY_ID must be specified");
+        }
+        if (!response.containsKey(RESPONSE_KEY_RESULT)) {
+            throw new IllegalArgumentException("RESPONSE_KEY_RESULT must be specified");
+        }
         try {
             if (mService != null) {
                 mService.notifyPermissionResponse(packageName, response);
@@ -444,22 +492,118 @@
      * in the manifest, or null if none was specified.
      */
     public List<RestrictionEntry> getManifestRestrictions(String packageName) {
-        // TODO:
-        return null;
+        ApplicationInfo appInfo = null;
+        try {
+            appInfo = mContext.getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_META_DATA);
+        } catch (NameNotFoundException pnfe) {
+            throw new IllegalArgumentException("No such package " + packageName);
+        }
+        if (appInfo == null || !appInfo.metaData.containsKey(META_DATA_APP_RESTRICTIONS)) {
+            return null;
+        }
+
+        XmlResourceParser xml =
+                appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS);
+        List<RestrictionEntry> restrictions = loadManifestRestrictions(packageName, xml);
+
+        return restrictions;
     }
 
-    private static class PermissionResponseCallbackWrapper
-            extends IPermissionResponseCallback.Stub {
+    private List<RestrictionEntry> loadManifestRestrictions(String packageName,
+            XmlResourceParser xml) {
+        Context appContext;
+        try {
+            appContext = mContext.createPackageContext(packageName, 0 /* flags */);
+        } catch (NameNotFoundException nnfe) {
+            return null;
+        }
+        ArrayList<RestrictionEntry> restrictions = new ArrayList<RestrictionEntry>();
+        RestrictionEntry restriction;
 
-        private PermissionResponseCallback mCallback;
-
-        PermissionResponseCallbackWrapper(PermissionResponseCallback callback) {
-            mCallback = callback;
+        try {
+            int tagType = xml.next();
+            while (tagType != XmlPullParser.END_DOCUMENT) {
+                if (tagType == XmlPullParser.START_TAG) {
+                    if (xml.getName().equals(TAG_RESTRICTION)) {
+                        AttributeSet attrSet = Xml.asAttributeSet(xml);
+                        if (attrSet != null) {
+                            TypedArray a = appContext.obtainStyledAttributes(attrSet,
+                                    com.android.internal.R.styleable.RestrictionEntry);
+                            restriction = loadRestriction(appContext, a);
+                            if (restriction != null) {
+                                restrictions.add(restriction);
+                            }
+                        }
+                    }
+                }
+                tagType = xml.next();
+            }
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "Reading restriction metadata for " + packageName, e);
+            return null;
+        } catch (IOException e) {
+            Log.w(TAG, "Reading restriction metadata for " + packageName, e);
+            return null;
         }
 
-        @Override
-        public void onResponse(Bundle response) {
-            mCallback.onResponse(response);
+        return restrictions;
+    }
+
+    private RestrictionEntry loadRestriction(Context appContext, TypedArray a) {
+        String key = a.getString(R.styleable.RestrictionEntry_key);
+        int restrictionType = a.getInt(
+                R.styleable.RestrictionEntry_restrictionType, -1);
+        String title = a.getString(R.styleable.RestrictionEntry_title);
+        String description = a.getString(R.styleable.RestrictionEntry_description);
+        int entries = a.getResourceId(R.styleable.RestrictionEntry_entries, 0);
+        int entryValues = a.getResourceId(R.styleable.RestrictionEntry_entryValues, 0);
+
+        if (restrictionType == -1) {
+            Log.w(TAG, "restrictionType cannot be omitted");
+            return null;
         }
+
+        if (key == null) {
+            Log.w(TAG, "key cannot be omitted");
+            return null;
+        }
+
+        RestrictionEntry restriction = new RestrictionEntry(restrictionType, key);
+        restriction.setTitle(title);
+        restriction.setDescription(description);
+        if (entries != 0) {
+            restriction.setChoiceEntries(appContext, entries);
+        }
+        if (entryValues != 0) {
+            restriction.setChoiceValues(appContext, entryValues);
+        }
+        // Extract the default value based on the type
+        switch (restrictionType) {
+            case RestrictionEntry.TYPE_NULL: // hidden
+            case RestrictionEntry.TYPE_STRING:
+            case RestrictionEntry.TYPE_CHOICE:
+                restriction.setSelectedString(
+                        a.getString(R.styleable.RestrictionEntry_defaultValue));
+                break;
+            case RestrictionEntry.TYPE_INTEGER:
+                restriction.setIntValue(
+                        a.getInt(R.styleable.RestrictionEntry_defaultValue, 0));
+                break;
+            case RestrictionEntry.TYPE_MULTI_SELECT:
+                int resId = a.getResourceId(R.styleable.RestrictionEntry_defaultValue, 0);
+                if (resId != 0) {
+                    restriction.setAllSelectedStrings(
+                            appContext.getResources().getStringArray(resId));
+                }
+                break;
+            case RestrictionEntry.TYPE_BOOLEAN:
+                restriction.setSelectedState(
+                        a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false));
+                break;
+            default:
+                Log.w(TAG, "Unknown restriction type " + restrictionType);
+        }
+        return restriction;
     }
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b93bbe0..95bd480 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -491,6 +491,23 @@
     public String nativeLibraryDir;
 
     /**
+     * Full path where unpacked native libraries for {@link #secondaryCpuAbi}
+     * are stored, if present.
+     *
+     * The main reason this exists is for bundled multi-arch apps, where
+     * it's not trivial to calculate the location of libs for the secondary abi
+     * given the location of the primary.
+     *
+     * TODO: Change the layout of bundled installs so that we can use
+     * nativeLibraryRootDir & nativeLibraryRootRequiresIsa there as well.
+     * (e.g {@code [ "/system/app-lib/Foo/arm", "/system/app-lib/Foo/arm64" ]}
+     * instead of {@code [ "/system/lib/Foo", "/system/lib64/Foo" ]}.
+     *
+     * @hide
+     */
+    public String secondaryNativeLibraryDir;
+
+    /**
      * The root path where unpacked native libraries are stored.
      * <p>
      * When {@link #nativeLibraryRootRequiresIsa} is set, the libraries are
@@ -530,23 +547,6 @@
     public String secondaryCpuAbi;
 
     /**
-     * The derived APK "root" for the given package. Will be non-null for bundled and
-     * updated system apps. This will be a top level path under which apks and libraries
-     * are installed, for eg. {@code /system}, {@code /oem} or {@code /vendor}. This is
-     * used to calculate the location of native code for a given package, for e.g
-     * {@code /vendor/lib} or {@code /vendor/lib64}.
-     *
-     * For app updates or fresh app installs, this will be {@code null} and we will use
-     * {@code legacyNativeLibraryDir}
-     *
-     * NOTE: This can be removed if we have a unified layout for bundled and installed
-     * apps.
-     *
-     * {@hide}
-     */
-    public String apkRoot;
-
-    /**
      * The kernel user-ID that has been assigned to this application;
      * currently this is not a unique ID (multiple applications can have
      * the same uid).
@@ -688,11 +688,11 @@
         splitSourceDirs = orig.splitSourceDirs;
         splitPublicSourceDirs = orig.splitPublicSourceDirs;
         nativeLibraryDir = orig.nativeLibraryDir;
+        secondaryNativeLibraryDir = orig.secondaryNativeLibraryDir;
         nativeLibraryRootDir = orig.nativeLibraryRootDir;
         nativeLibraryRootRequiresIsa = orig.nativeLibraryRootRequiresIsa;
         primaryCpuAbi = orig.primaryCpuAbi;
         secondaryCpuAbi = orig.secondaryCpuAbi;
-        apkRoot = orig.apkRoot;
         resourceDirs = orig.resourceDirs;
         seinfo = orig.seinfo;
         sharedLibraryFiles = orig.sharedLibraryFiles;
@@ -736,11 +736,11 @@
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
         dest.writeString(nativeLibraryDir);
+        dest.writeString(secondaryNativeLibraryDir);
         dest.writeString(nativeLibraryRootDir);
         dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0);
         dest.writeString(primaryCpuAbi);
         dest.writeString(secondaryCpuAbi);
-        dest.writeString(apkRoot);
         dest.writeStringArray(resourceDirs);
         dest.writeString(seinfo);
         dest.writeStringArray(sharedLibraryFiles);
@@ -783,11 +783,11 @@
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
         nativeLibraryDir = source.readString();
+        secondaryNativeLibraryDir = source.readString();
         nativeLibraryRootDir = source.readString();
         nativeLibraryRootRequiresIsa = source.readInt() != 0;
         primaryCpuAbi = source.readString();
         secondaryCpuAbi = source.readString();
-        apkRoot = source.readString();
         resourceDirs = source.readStringArray();
         seinfo = source.readString();
         sharedLibraryFiles = source.readStringArray();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 3a98f5d..eb46cf0 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -384,10 +384,16 @@
 
     /**
      * Ask the package manager to perform dex-opt (if needed) on the given
-     * package, if it already hasn't done mode.  Only does this if running
-     * in the special development "no pre-dexopt" mode.
+     * package and for the given instruction set if it already hasn't done
+     * so.
+     *
+     * If the supplied instructionSet is null, the package manager will use
+     * the packages default instruction set.
+     *
+     * In most cases, apps are dexopted in advance and this function will
+     * be a no-op.
      */
-    boolean performDexOpt(String packageName);
+    boolean performDexOptIfNeeded(String packageName, String instructionSet);
 
     /**
      * Update status of external media on the package manager to scan and
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 8f0c249..49ffef2 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -185,7 +185,9 @@
     public ConfigurationInfo[] configPreferences;
 
     /**
-     * The features that this application has said it requires.
+     * Features that this application has requested.
+     *
+     * @see FeatureInfo#FLAG_REQUIRED
      */
     public FeatureInfo[] reqFeatures;
 
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 50a0483..e336c5f 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -73,6 +73,7 @@
         dest.writeInt(versionCode);
         dest.writeInt(recommendedInstallLocation);
         dest.writeInt(installLocation);
+        dest.writeInt(multiArch ? 1 : 0);
 
         if (verifiers == null || verifiers.length == 0) {
             dest.writeInt(0);
@@ -98,6 +99,7 @@
         versionCode = source.readInt();
         recommendedInstallLocation = source.readInt();
         installLocation = source.readInt();
+        multiArch = (source.readInt() != 0);
 
         final int verifiersLength = source.readInt();
         if (verifiersLength == 0) {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 348a7ad..df82d26 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -42,17 +42,17 @@
  * intervention to complete the installation.
  * <p>
  * Sessions can install brand new apps, upgrade existing apps, or add new splits
- * onto an existing app.
+ * into an existing app.
  * <p>
- * Apps packaged into multiple split APKs always consist of a single "base" APK
+ * Apps packaged as multiple split APKs always consist of a single "base" APK
  * (with a {@code null} split name) and zero or more "split" APKs (with unique
  * split names). Any subset of these APKs can be installed together, as long as
  * the following constraints are met:
  * <ul>
  * <li>All APKs must have the exact same package name, version code, and signing
  * certificates.
- * <li>All installations must contain a single base APK.
  * <li>All APKs must have unique split names.
+ * <li>All installations must contain a single base APK.
  * </ul>
  */
 public class PackageInstaller {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 052c2ca..37df29a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1639,21 +1639,19 @@
     public abstract String[] canonicalToCurrentPackageNames(String[] names);
 
     /**
-     * Return a "good" intent to launch a front-door activity in a package,
-     * for use for example to implement an "open" button when browsing through
-     * packages.  The current implementation will look first for a main
-     * activity in the category {@link Intent#CATEGORY_INFO}, next for a
-     * main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
-     * null if neither are found.
-     *
-     * <p>Throws {@link NameNotFoundException} if a package with the given
-     * name cannot be found on the system.
+     * Returns a "good" intent to launch a front-door activity in a package.
+     * This is used, for example, to implement an "open" button when browsing
+     * through packages.  The current implementation looks first for a main
+     * activity in the category {@link Intent#CATEGORY_INFO}, and next for a
+     * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns
+     * <code>null</code> if neither are found.
      *
      * @param packageName The name of the package to inspect.
      *
-     * @return Returns either a fully-qualified Intent that can be used to
-     * launch the main activity in the package, or null if the package does
-     * not contain such an activity.
+     * @return A fully-qualified {@link Intent} that can be used to launch the
+     * main activity in the package. Returns <code>null</code> if the package
+     * does not contain such an activity, or if <em>packageName</em> is not
+     * recognized. 
      */
     public abstract Intent getLaunchIntentForPackage(String packageName);
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index db2da42..ab33d75 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -82,9 +82,22 @@
 import java.util.zip.ZipEntry;
 
 /**
- * Package archive parsing
+ * Parser for package files (APKs) on disk. This supports apps packaged either
+ * as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
+ * APKs in a single directory.
+ * <p>
+ * Apps packaged as multiple APKs always consist of a single "base" APK (with a
+ * {@code null} split name) and zero or more "split" APKs (with unique split
+ * names). Any subset of those split APKs are a valid install, as long as the
+ * following constraints are met:
+ * <ul>
+ * <li>All APKs must have the exact same package name, version code, and signing
+ * certificates.
+ * <li>All APKs must have unique split names.
+ * <li>All installations must contain a single base APK.
+ * </ul>
  *
- * {@hide}
+ * @hide
  */
 public class PackageParser {
     private static final boolean DEBUG_JAR = false;
@@ -92,6 +105,7 @@
     private static final boolean DEBUG_BACKUP = false;
 
     // TODO: switch outError users to PackageParserException
+    // TODO: refactor "codePath" to "apkPath"
 
     /** File name in an APK for the Android manifest. */
     private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
@@ -247,6 +261,7 @@
         /** Paths of any split APKs, ordered by parsed splitName */
         public final String[] splitCodePaths;
 
+        public final boolean coreApp;
         public final boolean multiArch;
 
         private PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
@@ -259,6 +274,7 @@
             this.codePath = codePath;
             this.baseCodePath = baseApk.codePath;
             this.splitCodePaths = splitCodePaths;
+            this.coreApp = baseApk.coreApp;
             this.multiArch = baseApk.multiArch;
         }
 
@@ -283,11 +299,12 @@
         public final int installLocation;
         public final VerifierInfo[] verifiers;
         public final Signature[] signatures;
+        public final boolean coreApp;
         public final boolean multiArch;
 
         public ApkLite(String codePath, String packageName, String splitName, int versionCode,
                 int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
-                boolean multiArch) {
+                boolean coreApp, boolean multiArch) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
@@ -295,6 +312,7 @@
             this.installLocation = installLocation;
             this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
             this.signatures = signatures;
+            this.coreApp = coreApp;
             this.multiArch = multiArch;
         }
     }
@@ -322,6 +340,11 @@
         mSeparateProcesses = procs;
     }
 
+    /**
+     * Flag indicating this parser should only consider apps with
+     * {@code coreApp} manifest attribute to be valid apps. This is useful when
+     * creating a minimalist boot environment.
+     */
     public void setOnlyCoreApps(boolean onlyCoreApps) {
         mOnlyCoreApps = onlyCoreApps;
     }
@@ -401,7 +424,7 @@
             pi.gids = gids;
         }
         if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
-            int N = p.configPreferences.size();
+            int N = p.configPreferences != null ? p.configPreferences.size() : 0;
             if (N > 0) {
                 pi.configPreferences = new ConfigurationInfo[N];
                 p.configPreferences.toArray(pi.configPreferences);
@@ -591,6 +614,17 @@
         }
     }
 
+    /**
+     * Parse only lightweight details about the package at the given location.
+     * Automatically detects if the package is a monolithic style (single APK
+     * file) or cluster style (directory of APKs).
+     * <p>
+     * This performs sanity checking on cluster style packages, such as
+     * requiring identical package name and version codes, a single base APK,
+     * and unique split names.
+     *
+     * @see PackageParser#parsePackage(File, int)
+     */
     public static PackageLite parsePackageLite(File packageFile, int flags)
             throws PackageParserException {
         if (packageFile.isDirectory()) {
@@ -677,6 +711,20 @@
         return new PackageLite(codePath, baseApk, splitNames, splitCodePaths);
     }
 
+    /**
+     * Parse the package at the given location. Automatically detects if the
+     * package is a monolithic style (single APK file) or cluster style
+     * (directory of APKs).
+     * <p>
+     * This performs sanity checking on cluster style packages, 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)}.
+     *
+     * @see #parsePackageLite(File, int)
+     */
     public Package parsePackage(File packageFile, int flags) throws PackageParserException {
         if (packageFile.isDirectory()) {
             return parseClusterPackage(packageFile, flags);
@@ -697,6 +745,11 @@
     private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
         final PackageLite lite = parseClusterPackageLite(packageDir, 0);
 
+        if (mOnlyCoreApps && !lite.coreApp) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                    "Not a coreApp: " + packageDir);
+        }
+
         final File baseApk = new File(lite.baseCodePath);
         final Package pkg = parseBaseApk(baseApk, flags);
         if (pkg == null) {
@@ -705,12 +758,13 @@
         }
 
         if (!ArrayUtils.isEmpty(lite.splitNames)) {
+            final int num = lite.splitNames.length;
             pkg.splitNames = lite.splitNames;
             pkg.splitCodePaths = lite.splitCodePaths;
+            pkg.splitFlags = new int[num];
 
-            for (String splitCodePath : lite.splitCodePaths) {
-                final File splitApk = new File(splitCodePath);
-                parseSplitApk(pkg, splitApk, flags);
+            for (int i = 0; i < num; i++) {
+                parseSplitApk(pkg, i, flags);
             }
         }
 
@@ -730,115 +784,195 @@
      */
     @Deprecated
     public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
-        final Package pkg = parseBaseApk(apkFile, flags);
-        if (pkg == null) {
-            throw new PackageParserException(mParseError, "Failed to parse " + apkFile);
+        if (mOnlyCoreApps) {
+            final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
+            if (!lite.coreApp) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                        "Not a coreApp: " + apkFile);
+            }
         }
 
+        final Package pkg = parseBaseApk(apkFile, flags);
         pkg.codePath = apkFile.getAbsolutePath();
         return pkg;
     }
 
-    private Package parseBaseApk(File apkFile, int flags) {
-        final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0;
+    private Package parseBaseApk(File apkFile, int flags) throws PackageParserException {
+        final String apkPath = apkFile.getAbsolutePath();
 
         mParseError = PackageManager.INSTALL_SUCCEEDED;
-
-        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 (!isApkFile(apkFile) && (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: " + apkPath);
-            }
-            mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
-            return null;
+
+        if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkFile(apkFile)) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "Invalid package file: " + apkPath);
         }
 
-        if (DEBUG_JAR)
-            Slog.d(TAG, "Scanning package: " + apkPath);
+        if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
-        XmlResourceParser parser = null;
-        AssetManager assmgr = null;
+        AssetManager assets = null;
         Resources res = null;
-        boolean assetError = true;
+        XmlResourceParser parser = null;
         try {
-            assmgr = new AssetManager();
-            int cookie = assmgr.addAssetPath(apkPath);
-            if (cookie != 0) {
-                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:" + apkPath);
+            assets = new AssetManager();
+            int cookie = assets.addAssetPath(apkPath);
+            if (cookie == 0) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                        "Failed adding asset path: " + apkPath);
             }
-        } catch (Exception e) {
-            Slog.w(TAG, "Unable to read AndroidManifest.xml of " + apkPath, e);
-        }
-        if (assetError) {
-            if (assmgr != null) assmgr.close();
-            mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
-            return null;
-        }
-        String[] errorText = new String[1];
-        Package pkg = null;
-        Exception errorException = null;
-        try {
-            // XXXX todo: need to figure out correct configuration.
-            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, apkPath, errorException);
-                } else {
-                    Slog.w(TAG, apkPath + " (at "
-                            + parser.getPositionDescription()
-                            + "): " + errorText[0]);
-                }
-                if (mParseError == PackageManager.INSTALL_SUCCEEDED) {
-                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
-                }
+            res = new Resources(assets, mMetrics, null);
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+
+            final String[] outError = new String[1];
+            final Package pkg = parseBaseApk(res, parser, flags, outError);
+            if (pkg == null) {
+                throw new PackageParserException(mParseError,
+                        apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
             }
-            parser.close();
-            assmgr.close();
-            return null;
+
+            pkg.baseCodePath = apkPath;
+            pkg.mSignatures = null;
+
+            // TODO: Remove this when the WebView can load resources dynamically. b/11505352
+            pkg.usesOptionalLibraries = ArrayUtils.add(pkg.usesOptionalLibraries,
+                    "com.android.webview");
+
+            return pkg;
+
+        } catch (PackageParserException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "Failed to read manifest from " + apkPath, e);
+        } finally {
+            IoUtils.closeQuietly(parser);
+            IoUtils.closeQuietly(assets);
         }
-
-        parser.close();
-        assmgr.close();
-
-        pkg.baseCodePath = apkPath;
-        pkg.mSignatures = null;
-
-        // TODO: Remove this when the WebView can load resources dynamically. b/11505352
-        if (pkg.usesOptionalLibraries == null) {
-            pkg.usesOptionalLibraries = new ArrayList<String>();
-        }
-        pkg.usesOptionalLibraries.add("com.android.webview");
-
-        return pkg;
     }
 
-    private void parseSplitApk(Package pkg, File apkFile, int flags) throws PackageParserException {
-        final String splitCodePath = apkFile.getAbsolutePath();
-        mArchiveSourcePath = apkFile.getAbsolutePath();
+    private void parseSplitApk(Package pkg, int splitIndex, int flags)
+            throws PackageParserException {
+        final String apkPath = pkg.splitCodePaths[splitIndex];
+        final File apkFile = new File(apkPath);
 
-        // TODO: expand split APK parsing
+        mParseError = PackageManager.INSTALL_SUCCEEDED;
+        mArchiveSourcePath = apkPath;
+
+        if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkFile(apkFile)) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "Invalid package file: " + apkPath);
+        }
+
+        if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
+
+        AssetManager assets = null;
+        Resources res = null;
+        XmlResourceParser parser = null;
+        try {
+            assets = new AssetManager();
+            int cookie = assets.addAssetPath(apkPath);
+            if (cookie == 0) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                        "Failed adding asset path: " + apkPath);
+            }
+
+            res = new Resources(assets, mMetrics, null);
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+
+            final String[] outError = new String[1];
+            pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError);
+            if (pkg == null) {
+                throw new PackageParserException(mParseError,
+                        apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
+            }
+
+        } catch (PackageParserException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "Failed to read manifest from " + apkPath, e);
+        } finally {
+            IoUtils.closeQuietly(parser);
+            IoUtils.closeQuietly(assets);
+        }
+    }
+
+    /**
+     * Parse the manifest of a <em>split APK</em>.
+     * <p>
+     * Note that split APKs have many more restrictions on what they're capable
+     * of doing, so many valid features of a base APK have been carefully
+     * omitted here.
+     */
+    private Package parseSplitApk(Package pkg, Resources res, XmlResourceParser parser, int flags,
+            int splitIndex, String[] outError) throws XmlPullParserException, IOException,
+            PackageParserException {
+        AttributeSet attrs = parser;
+
+        // We parsed manifest tag earlier; just skip past it
+        parsePackageSplitNames(parser, attrs, flags);
+
+        mParseInstrumentationArgs = null;
+        mParseActivityArgs = null;
+        mParseServiceArgs = null;
+        mParseProviderArgs = null;
+
+        int type;
+
+        boolean foundApp = false;
+
+        int outerDepth = parser.getDepth();
+        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("application")) {
+                if (foundApp) {
+                    if (RIGID_PARSER) {
+                        outError[0] = "<manifest> has more than one <application>";
+                        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                        return null;
+                    } else {
+                        Slog.w(TAG, "<manifest> has more than one <application>");
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                }
+
+                foundApp = true;
+                if (!parseSplitApplication(pkg, res, parser, attrs, flags, splitIndex, outError)) {
+                    return null;
+                }
+
+            } else if (RIGID_PARSER) {
+                outError[0] = "Bad element under <manifest>: "
+                    + parser.getName();
+                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                return null;
+
+            } else {
+                Slog.w(TAG, "Unknown element under <manifest>: " + parser.getName()
+                        + " at " + mArchiveSourcePath + " "
+                        + parser.getPositionDescription());
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+        }
+
+        if (!foundApp) {
+            outError[0] = "<manifest> does not contain an <application>";
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY;
+        }
+
+        return pkg;
     }
 
     /**
@@ -1001,14 +1135,14 @@
             throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
-        AssetManager assmgr = null;
+        AssetManager assets = null;
         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,
+            assets = new AssetManager();
+            assets.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(apkPath);
+            int cookie = assets.addAssetPath(apkPath);
             if (cookie == 0) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                         "Failed to parse " + apkPath);
@@ -1017,8 +1151,8 @@
             final DisplayMetrics metrics = new DisplayMetrics();
             metrics.setToDefaults();
 
-            final Resources res = new Resources(assmgr, metrics, null);
-            parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+            final Resources res = new Resources(assets, metrics, null);
+            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
 
             // Only collect certificates on the manifest; does not validate
             // signatures across remainder of package.
@@ -1036,8 +1170,8 @@
             throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                     "Failed to parse " + apkPath, e);
         } finally {
-            if (parser != null) parser.close();
-            if (assmgr != null) assmgr.close();
+            IoUtils.closeQuietly(parser);
+            IoUtils.closeQuietly(assets);
         }
     }
 
@@ -1118,8 +1252,10 @@
 
         int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
         int versionCode = 0;
-        int numFound = 0;
+        boolean coreApp = false;
         boolean multiArch = false;
+
+        int numFound = 0;
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
             String attr = attrs.getAttributeName(i);
             if (attr.equals("installLocation")) {
@@ -1129,8 +1265,8 @@
             } else if (attr.equals("versionCode")) {
                 versionCode = attrs.getAttributeIntValue(i, 0);
                 numFound++;
-            } else if (attr.equals("multiArch")) {
-                multiArch = attrs.getAttributeBooleanValue(i, false);
+            } else if (attr.equals("coreApp")) {
+                coreApp = attrs.getAttributeBooleanValue(i, false);
                 numFound++;
             }
             if (numFound >= 3) {
@@ -1155,10 +1291,20 @@
                     verifiers.add(verifier);
                 }
             }
+
+            if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) {
+                for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+                    final String attr = attrs.getAttributeName(i);
+                    if ("multiArch".equals(attr)) {
+                        multiArch = attrs.getAttributeBooleanValue(i, false);
+                        break;
+                    }
+                }
+            }
         }
 
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
-                installLocation, verifiers, signatures, multiArch);
+                installLocation, verifiers, signatures, coreApp, multiArch);
     }
 
     /**
@@ -1173,8 +1319,16 @@
         return new Signature(sig);
     }
 
+    /**
+     * Parse the manifest of a <em>base APK</em>.
+     * <p>
+     * When adding new features, carefully consider if they should also be
+     * supported by split APKs.
+     */
     private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
-            boolean trustedOverlay, String[] outError) throws XmlPullParserException, IOException {
+            String[] outError) throws XmlPullParserException, IOException {
+        final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0;
+
         AttributeSet attrs = parser;
 
         mParseInstrumentationArgs = null;
@@ -1195,14 +1349,6 @@
 
         int type;
 
-        if (mOnlyCoreApps) {
-            boolean core = attrs.getAttributeBooleanValue(null, "coreApp", false);
-            if (!core) {
-                mParseError = PackageManager.INSTALL_SUCCEEDED;
-                return null;
-            }
-        }
-
         if (!TextUtils.isEmpty(splitName)) {
             outError[0] = "Expected base APK, but found split " + splitName;
             mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
@@ -1283,7 +1429,7 @@
                 }
 
                 foundApp = true;
-                if (!parseApplication(pkg, res, parser, attrs, flags, outError)) {
+                if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
                     return null;
                 }
             } else if (tagName.equals("overlay")) {
@@ -1355,7 +1501,7 @@
                     cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
                 }
                 sa.recycle();
-                pkg.configPreferences.add(cPref);
+                pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref);
 
                 XmlUtils.skipCurrentTag(parser);
 
@@ -1378,15 +1524,12 @@
                     fi.flags |= FeatureInfo.FLAG_REQUIRED;
                 }
                 sa.recycle();
-                if (pkg.reqFeatures == null) {
-                    pkg.reqFeatures = new ArrayList<FeatureInfo>();
-                }
-                pkg.reqFeatures.add(fi);
-                
+                pkg.reqFeatures = ArrayUtils.add(pkg.reqFeatures, fi);
+
                 if (fi.name == null) {
                     ConfigurationInfo cPref = new ConfigurationInfo();
                     cPref.reqGlEsVersion = fi.reqGlEsVersion;
-                    pkg.configPreferences.add(cPref);
+                    pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref);
                 }
 
                 XmlUtils.skipCurrentTag(parser);
@@ -2197,7 +2340,14 @@
         return a;
     }
 
-    private boolean parseApplication(Package owner, Resources res,
+    /**
+     * Parse the {@code application} XML tree at the current parse location in a
+     * <em>base APK</em> manifest.
+     * <p>
+     * When adding new features, carefully consider if they should also be
+     * supported by split APKs.
+     */
+    private boolean parseBaseApplication(Package owner, Resources res,
             XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
         throws XmlPullParserException, IOException {
         final ApplicationInfo ai = owner.applicationInfo;
@@ -2317,7 +2467,7 @@
             ai.flags |= ApplicationInfo.FLAG_VM_SAFE_MODE;
         }
 
-        boolean hardwareAccelerated = sa.getBoolean(
+        owner.baseHardwareAccelerated = sa.getBoolean(
                 com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
                 owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
 
@@ -2442,7 +2592,7 @@
             String tagName = parser.getName();
             if (tagName.equals("activity")) {
                 Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
-                        hardwareAccelerated);
+                        owner.baseHardwareAccelerated);
                 if (a == null) {
                     mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                     return false;
@@ -2508,11 +2658,9 @@
                 sa.recycle();
 
                 if (lname != null) {
-                    if (owner.libraryNames == null) {
-                        owner.libraryNames = new ArrayList<String>();
-                    }
-                    if (!owner.libraryNames.contains(lname)) {
-                        owner.libraryNames.add(lname.intern());
+                    lname = lname.intern();
+                    if (!ArrayUtils.contains(owner.libraryNames, lname)) {
+                        owner.libraryNames = ArrayUtils.add(owner.libraryNames, lname);
                     }
                 }
 
@@ -2533,19 +2681,150 @@
                 sa.recycle();
 
                 if (lname != null) {
+                    lname = lname.intern();
                     if (req) {
-                        if (owner.usesLibraries == null) {
-                            owner.usesLibraries = new ArrayList<String>();
-                        }
-                        if (!owner.usesLibraries.contains(lname)) {
-                            owner.usesLibraries.add(lname.intern());
-                        }
+                        owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname);
                     } else {
-                        if (owner.usesOptionalLibraries == null) {
-                            owner.usesOptionalLibraries = new ArrayList<String>();
-                        }
-                        if (!owner.usesOptionalLibraries.contains(lname)) {
-                            owner.usesOptionalLibraries.add(lname.intern());
+                        owner.usesOptionalLibraries = ArrayUtils.add(
+                                owner.usesOptionalLibraries, lname);
+                    }
+                }
+
+                XmlUtils.skipCurrentTag(parser);
+
+            } else if (tagName.equals("uses-package")) {
+                // Dependencies for app installers; we don't currently try to
+                // enforce this.
+                XmlUtils.skipCurrentTag(parser);
+
+            } else {
+                if (!RIGID_PARSER) {
+                    Slog.w(TAG, "Unknown element under <application>: " + tagName
+                            + " at " + mArchiveSourcePath + " "
+                            + parser.getPositionDescription());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                } else {
+                    outError[0] = "Bad element under <application>: " + tagName;
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Parse the {@code application} XML tree at the current parse location in a
+     * <em>split APK</em> manifest.
+     * <p>
+     * Note that split APKs have many more restrictions on what they're capable
+     * of doing, so many valid features of a base APK have been carefully
+     * omitted here.
+     */
+    private boolean parseSplitApplication(Package owner, Resources res, XmlPullParser parser,
+            AttributeSet attrs, int flags, int splitIndex, String[] outError)
+            throws XmlPullParserException, IOException {
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestApplication);
+
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_hasCode, true)) {
+            owner.splitFlags[splitIndex] |= ApplicationInfo.FLAG_HAS_CODE;
+        }
+
+        final int innerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("activity")) {
+                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
+                        owner.baseHardwareAccelerated);
+                if (a == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.activities.add(a);
+
+            } else if (tagName.equals("receiver")) {
+                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
+                if (a == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.receivers.add(a);
+
+            } else if (tagName.equals("service")) {
+                Service s = parseService(owner, res, parser, attrs, flags, outError);
+                if (s == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.services.add(s);
+
+            } else if (tagName.equals("provider")) {
+                Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
+                if (p == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.providers.add(p);
+
+            } else if (tagName.equals("activity-alias")) {
+                Activity a = parseActivityAlias(owner, res, parser, attrs, flags, outError);
+                if (a == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+                owner.activities.add(a);
+
+            } else if (parser.getName().equals("meta-data")) {
+                // note: application meta-data is stored off to the side, so it can
+                // remain null in the primary copy (we like to avoid extra copies because
+                // it can be large)
+                if ((owner.mAppMetaData = parseMetaData(res, parser, attrs, owner.mAppMetaData,
+                        outError)) == null) {
+                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                    return false;
+                }
+
+            } else if (tagName.equals("uses-library")) {
+                sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestUsesLibrary);
+
+                // Note: don't allow this value to be a reference to a resource
+                // that may change.
+                String lname = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
+                boolean req = sa.getBoolean(
+                        com.android.internal.R.styleable.AndroidManifestUsesLibrary_required,
+                        true);
+
+                sa.recycle();
+
+                if (lname != null) {
+                    lname = lname.intern();
+                    if (req) {
+                        // Upgrade to treat as stronger constraint
+                        owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname);
+                        owner.usesOptionalLibraries = ArrayUtils.remove(
+                                owner.usesOptionalLibraries, lname);
+                    } else {
+                        // Ignore if someone already defined as required
+                        if (!ArrayUtils.contains(owner.usesLibraries, lname)) {
+                            owner.usesOptionalLibraries = ArrayUtils.add(
+                                    owner.usesOptionalLibraries, lname);
                         }
                     }
                 }
@@ -3866,6 +4145,11 @@
         /** Paths of any split APKs, ordered by parsed splitName */
         public String[] splitCodePaths;
 
+        /** Flags of any split APKs; ordered by parsed splitName */
+        public int[] splitFlags;
+
+        public boolean baseHardwareAccelerated;
+
         // For now we only support one application per package.
         public final ApplicationInfo applicationInfo = new ApplicationInfo();
 
@@ -3917,7 +4201,7 @@
         public int mPreferredOrder = 0;
 
         // For use by package manager to keep track of where it needs to do dexopt.
-        public boolean mDexOptNeeded = true;
+        public final ArraySet<String> mDexOptPerformed = new ArraySet<>(4);
 
         // For use by package manager to keep track of when a package was last used.
         public long mLastPackageUsageTimeInMills;
@@ -3934,15 +4218,10 @@
         // Whether an operation is currently pending on this package
         public boolean mOperationPending;
 
-        /*
-         *  Applications hardware preferences
-         */
-        public final ArrayList<ConfigurationInfo> configPreferences =
-                new ArrayList<ConfigurationInfo>();
+        // Applications hardware preferences
+        public ArrayList<ConfigurationInfo> configPreferences = null;
 
-        /*
-         *  Applications requested features
-         */
+        // Applications requested features
         public ArrayList<FeatureInfo> reqFeatures = null;
 
         public int installLocation;
@@ -3991,6 +4270,25 @@
             return paths;
         }
 
+        /**
+         * Filtered set of {@link #getAllCodePaths()} that excludes
+         * resource-only APKs.
+         */
+        public List<String> getAllCodePathsExcludingResourceOnly() {
+            ArrayList<String> paths = new ArrayList<>();
+            if ((applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+                paths.add(baseCodePath);
+            }
+            if (!ArrayUtils.isEmpty(splitCodePaths)) {
+                for (int i = 0; i < splitCodePaths.length; i++) {
+                    if ((splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+                        paths.add(splitCodePaths[i]);
+                    }
+                }
+            }
+            return paths;
+        }
+
         public void setPackageName(String newName) {
             packageName = newName;
             applicationInfo.packageName = newName;
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 0c04401..3a30123 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -33,7 +33,7 @@
  * files that have been bundled with the application as a simple stream of
  * bytes.
  */
-public final class AssetManager {
+public final class AssetManager implements AutoCloseable {
     /* modes used when opening an asset */
 
     /**
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 9625578..38ddede 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1711,7 +1711,7 @@
 
             String locale = null;
             if (mConfiguration.locale != null) {
-                locale = adjustLanguageTag(localeToLanguageTag(mConfiguration.locale));
+                locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());
             }
             int width, height;
             if (mMetrics.widthPixels >= mMetrics.heightPixels) {
@@ -1800,12 +1800,6 @@
         }
     }
 
-    // Locale.toLanguageTag() is not available in Java6. LayoutLib overrides
-    // this method to enable users to use Java6.
-    private String localeToLanguageTag(Locale locale) {
-        return locale.toLanguageTag();
-    }
-
     /**
      * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
      * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 4c73e6a..eadfa73 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -2423,6 +2423,19 @@
             return Camera.this;
         }
 
+
+        /**
+         * Value equality check.
+         *
+         * @hide
+         */
+        public boolean same(Parameters other) {
+            if (this == other) {
+                return true;
+            }
+            return other != null && Parameters.this.mMap.equals(other.mMap);
+        }
+
         /**
          * Writes the current Parameters to the log.
          * @hide
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 4b1659f..4976a48 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -131,6 +131,7 @@
     }
 
     private final CameraMetadataNative mProperties;
+    private List<CameraCharacteristics.Key<?>> mKeys;
     private List<CaptureRequest.Key<?>> mAvailableRequestKeys;
     private List<CaptureResult.Key<?>> mAvailableResultKeys;
 
@@ -194,8 +195,20 @@
      */
     @Override
     public List<Key<?>> getKeys() {
-        // Force the javadoc for this function to show up on the CameraCharacteristics page
-        return super.getKeys();
+        // List of keys is immutable; cache the results after we calculate them
+        if (mKeys != null) {
+            return mKeys;
+        }
+
+        int[] filterTags = get(REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+        if (filterTags == null) {
+            throw new AssertionError("android.request.availableCharacteristicsKeys must be non-null"
+                    + " in the characteristics");
+        }
+
+        mKeys = Collections.unmodifiableList(
+                getKeysStatic(getClass(), getKeyClass(), this, filterTags));
+        return mKeys;
     }
 
     /**
@@ -218,8 +231,13 @@
             Object crKey = CaptureRequest.Key.class;
             Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey;
 
-            mAvailableRequestKeys = Collections.unmodifiableList(
-                    getAvailableKeyList(CaptureRequest.class, crKeyTyped));
+            int[] filterTags = get(REQUEST_AVAILABLE_REQUEST_KEYS);
+            if (filterTags == null) {
+                throw new AssertionError("android.request.availableRequestKeys must be non-null "
+                        + "in the characteristics");
+            }
+            mAvailableRequestKeys =
+                    getAvailableKeyList(CaptureRequest.class, crKeyTyped, filterTags);
         }
         return mAvailableRequestKeys;
     }
@@ -244,8 +262,12 @@
             Object crKey = CaptureResult.Key.class;
             Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey;
 
-            mAvailableResultKeys = Collections.unmodifiableList(
-                    getAvailableKeyList(CaptureResult.class, crKeyTyped));
+            int[] filterTags = get(REQUEST_AVAILABLE_RESULT_KEYS);
+            if (filterTags == null) {
+                throw new AssertionError("android.request.availableResultKeys must be non-null "
+                        + "in the characteristics");
+            }
+            mAvailableResultKeys = getAvailableKeyList(CaptureResult.class, crKeyTyped, filterTags);
         }
         return mAvailableResultKeys;
     }
@@ -266,7 +288,7 @@
      * @throws IllegalArgumentException if metadataClass is not a subclass of CameraMetadata
      */
     private <TKey> List<TKey>
-    getAvailableKeyList(Class<?> metadataClass, Class<TKey> keyClass) {
+    getAvailableKeyList(Class<?> metadataClass, Class<TKey> keyClass, int[] filterTags) {
 
         if (metadataClass.equals(CameraMetadata.class)) {
             throw new AssertionError(
@@ -277,7 +299,7 @@
         }
 
         List<TKey> staticKeyList = CameraCharacteristics.<TKey>getKeysStatic(
-                metadataClass, keyClass, /*instance*/null);
+                metadataClass, keyClass, /*instance*/null, filterTags);
         return Collections.unmodifiableList(staticKeyList);
     }
 
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index fa35f44..36b1089 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -16,11 +16,13 @@
 
 package android.hardware.camera2;
 
+import android.hardware.camera2.impl.CameraMetadataNative;
 import android.util.Log;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -101,7 +103,8 @@
     @SuppressWarnings("unchecked")
     public List<TKey> getKeys() {
         Class<CameraMetadata<TKey>> thisClass = (Class<CameraMetadata<TKey>>) getClass();
-        return Collections.unmodifiableList(getKeysStatic(thisClass, getKeyClass(), this));
+        return Collections.unmodifiableList(
+                getKeysStatic(thisClass, getKeyClass(), this, /*filterTags*/null));
     }
 
     /**
@@ -111,14 +114,25 @@
      * <p>
      * Optionally, if {@code instance} is not null, then filter out any keys with null values.
      * </p>
+     *
+     * <p>
+     * Optionally, if {@code filterTags} is not {@code null}, then filter out any keys
+     * whose native {@code tag} is not in {@code filterTags}. The {@code filterTags} array will be
+     * sorted as a side effect.
+     * </p>
      */
      /*package*/ @SuppressWarnings("unchecked")
     static <TKey> ArrayList<TKey> getKeysStatic(
              Class<?> type, Class<TKey> keyClass,
-             CameraMetadata<TKey> instance) {
+             CameraMetadata<TKey> instance,
+             int[] filterTags) {
 
         if (VERBOSE) Log.v(TAG, "getKeysStatic for " + type);
 
+        if (filterTags != null) {
+            Arrays.sort(filterTags);
+        }
+
         ArrayList<TKey> keyList = new ArrayList<TKey>();
 
         Field[] fields = type.getDeclaredFields();
@@ -137,7 +151,15 @@
                 }
 
                 if (instance == null || instance.getProtected(key) != null) {
-                    keyList.add(key);
+                    if (shouldKeyBeAdded(key, filterTags)) {
+                        keyList.add(key);
+
+                        if (VERBOSE) {
+                            Log.v(TAG, "getKeysStatic - key was added - " + key);
+                        }
+                    } else if (VERBOSE) {
+                        Log.v(TAG, "getKeysStatic - key was filtered - " + key);
+                    }
                 }
             }
         }
@@ -145,6 +167,39 @@
         return keyList;
     }
 
+    @SuppressWarnings("rawtypes")
+    private static <TKey> boolean shouldKeyBeAdded(TKey key, int[] filterTags) {
+        if (key == null) {
+            throw new NullPointerException("key must not be null");
+        }
+
+        CameraMetadataNative.Key nativeKey;
+
+        /*
+         * Get the native key from the public api key
+         */
+        if (key instanceof CameraCharacteristics.Key) {
+            nativeKey = ((CameraCharacteristics.Key)key).getNativeKey();
+        } else if (key instanceof CaptureResult.Key) {
+            nativeKey = ((CaptureResult.Key)key).getNativeKey();
+        } else if (key instanceof CaptureRequest.Key) {
+            nativeKey = ((CaptureRequest.Key)key).getNativeKey();
+        } else {
+            // Reject fields that aren't a key
+            throw new IllegalArgumentException("key type must be that of a metadata key");
+        }
+
+        // No filtering necessary
+        if (filterTags == null) {
+            return true;
+        }
+
+        int keyTag = nativeKey.getTag();
+
+        // non-negative result is returned iff the value is in the array
+        return Arrays.binarySearch(filterTags, keyTag) >= 0;
+    }
+
     /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * The enum values below this point are generated from metadata
      * definitions in /system/media/camera/docs. Do not modify by hand or
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 6de5c25..ebab563 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -56,7 +56,6 @@
 import android.os.Parcelable;
 import android.os.Parcel;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Size;
 
 import com.android.internal.util.Preconditions;
@@ -79,7 +78,7 @@
         private final Class<T> mType;
         private final TypeReference<T> mTypeReference;
         private final String mName;
-
+        private final int mHash;
         /**
          * Visible for testing only.
          *
@@ -95,6 +94,7 @@
             mName = name;
             mType = type;
             mTypeReference = TypeReference.createSpecializedTypeReference(type);
+            mHash = mName.hashCode() ^ mTypeReference.hashCode();
         }
 
         /**
@@ -113,6 +113,7 @@
             mName = name;
             mType = (Class<T>)typeReference.getRawType();
             mTypeReference = typeReference;
+            mHash = mName.hashCode() ^ mTypeReference.hashCode();
         }
 
         /**
@@ -137,7 +138,7 @@
          */
         @Override
         public final int hashCode() {
-            return mName.hashCode() ^ mTypeReference.hashCode();
+            return mHash;
         }
 
         /**
@@ -156,6 +157,10 @@
                 return true;
             }
 
+            if (o == null || this.hashCode() != o.hashCode()) {
+                return false;
+            }
+
             Key<?> lhs;
 
             if (o instanceof CaptureResult.Key) {
@@ -337,11 +342,11 @@
     public <T> T get(Key<T> key) {
         Preconditions.checkNotNull(key, "key must not be null");
 
-        Pair<T, Boolean> override = getOverride(key);
-        if (override.second) {
-            return override.first;
+        // Check if key has been overridden to use a wrapper class on the java side.
+        GetCommand g = sGetCommandMap.get(key);
+        if (g != null) {
+            return (T) g.getValue(this, key);
         }
-
         return getBase(key);
     }
 
@@ -371,7 +376,9 @@
      * type to the key.
      */
     public <T> void set(Key<T> key, T value) {
-        if (setOverride(key, value)) {
+        SetCommand s = sSetCommandMap.get(key);
+        if (s != null) {
+            s.setValue(this, value);
             return;
         }
 
@@ -449,44 +456,119 @@
         ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
         return marshaler.unmarshal(buffer);
     }
-    // Need overwrite some metadata that has different definitions between native
-    // and managed sides.
-    @SuppressWarnings("unchecked")
-    private <T> Pair<T, Boolean> getOverride(Key<T> key) {
-        T value = null;
-        boolean override = true;
 
-        if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) {
-            value = (T) getAvailableFormats();
-        } else if (key.equals(CaptureResult.STATISTICS_FACES)) {
-            value = (T) getFaces();
-        } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
-            value = (T) getFaceRectangles();
-        } else if (key.equals(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)) {
-            value = (T) getStreamConfigurationMap();
-        } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) {
-            value = (T) getMaxRegions(key);
-        } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) {
-            value = (T) getMaxRegions(key);
-        } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) {
-            value = (T) getMaxRegions(key);
-        } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) {
-            value = (T) getMaxNumOutputs(key);
-        } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) {
-            value = (T) getMaxNumOutputs(key);
-        } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) {
-            value = (T) getMaxNumOutputs(key);
-        } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) {
-            value = (T) getTonemapCurve();
-        } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) {
-            value = (T) getGpsLocation();
-        } else if (key.equals(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP)) {
-            value = (T) getLensShadingMap();
-        } else {
-            override = false;
-        }
-
-        return Pair.create(value, override);
+    // Use Command pattern here to avoid lots of expensive if/equals checks in get for overridden
+    // metadata.
+    private static final HashMap<Key<?>, GetCommand> sGetCommandMap =
+            new HashMap<Key<?>, GetCommand>();
+    static {
+        sGetCommandMap.put(
+                CameraCharacteristics.SCALER_AVAILABLE_FORMATS.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getAvailableFormats();
+                    }
+                });
+        sGetCommandMap.put(
+                CaptureResult.STATISTICS_FACES.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getFaceRectangles();
+                    }
+                });
+        sGetCommandMap.put(
+                CaptureResult.STATISTICS_FACE_RECTANGLES.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getFaces();
+                    }
+                });
+        sGetCommandMap.put(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP.getNativeKey(),
+                        new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getStreamConfigurationMap();
+                    }
+                });
+        sGetCommandMap.put(
+                CameraCharacteristics.CONTROL_MAX_REGIONS_AE.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getMaxRegions(key);
+                    }
+                });
+        sGetCommandMap.put(
+                CameraCharacteristics.CONTROL_MAX_REGIONS_AWB.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getMaxRegions(key);
+                    }
+                });
+        sGetCommandMap.put(
+                CameraCharacteristics.CONTROL_MAX_REGIONS_AF.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getMaxRegions(key);
+                    }
+                });
+        sGetCommandMap.put(
+                CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getMaxNumOutputs(key);
+                    }
+                });
+        sGetCommandMap.put(
+                CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getMaxNumOutputs(key);
+                    }
+                });
+        sGetCommandMap.put(
+                CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING.getNativeKey(),
+                        new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getMaxNumOutputs(key);
+                    }
+                });
+        sGetCommandMap.put(
+                CaptureRequest.TONEMAP_CURVE.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getTonemapCurve();
+                    }
+                });
+        sGetCommandMap.put(
+                CaptureResult.JPEG_GPS_LOCATION.getNativeKey(), new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getGpsLocation();
+                    }
+                });
+        sGetCommandMap.put(
+                CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP.getNativeKey(),
+                        new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getLensShadingMap();
+                    }
+                });
     }
 
     private int[] getAvailableFormats() {
@@ -759,19 +841,37 @@
         writeValues(tag, values);
     }
 
-    // Set the camera metadata override.
-    private <T> boolean setOverride(Key<T> key, T value) {
-        if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) {
-            return setAvailableFormats((int[]) value);
-        } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
-            return setFaceRectangles((Rect[]) value);
-        } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) {
-            return setTonemapCurve((TonemapCurve) value);
-        } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) {
-            return setGpsLocation((Location) value);
-        }
-        // For other keys, set() falls back to setBase().
-        return false;
+    // Use Command pattern here to avoid lots of expensive if/equals checks in get for overridden
+    // metadata.
+    private static final HashMap<Key<?>, SetCommand> sSetCommandMap =
+            new HashMap<Key<?>, SetCommand>();
+    static {
+        sSetCommandMap.put(CameraCharacteristics.SCALER_AVAILABLE_FORMATS.getNativeKey(),
+                new SetCommand() {
+            @Override
+            public <T> void setValue(CameraMetadataNative metadata, T value) {
+                metadata.setAvailableFormats((int[]) value);
+            }
+        });
+        sSetCommandMap.put(CaptureResult.STATISTICS_FACE_RECTANGLES.getNativeKey(),
+                new SetCommand() {
+            @Override
+            public <T> void setValue(CameraMetadataNative metadata, T value) {
+                metadata.setFaceRectangles((Rect[]) value);
+            }
+        });
+        sSetCommandMap.put(CaptureRequest.TONEMAP_CURVE.getNativeKey(), new SetCommand() {
+            @Override
+            public <T> void setValue(CameraMetadataNative metadata, T value) {
+                metadata.setTonemapCurve((TonemapCurve) value);
+            }
+        });
+        sSetCommandMap.put(CaptureResult.JPEG_GPS_LOCATION.getNativeKey(), new SetCommand() {
+            @Override
+            public <T> void setValue(CameraMetadataNative metadata, T value) {
+                metadata.setGpsLocation((Location) value);
+            }
+        });
     }
 
     private boolean setAvailableFormats(int[] value) {
diff --git a/core/java/android/hardware/camera2/impl/GetCommand.java b/core/java/android/hardware/camera2/impl/GetCommand.java
new file mode 100644
index 0000000..a3c677a
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/GetCommand.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.impl;
+
+/**
+ * Getter interface for use with Command pattern metadata value getters.
+ */
+public interface GetCommand {
+
+    /**
+     * Get the value from the given {@link CameraMetadataNative} object.
+     *
+     * @param metadata the {@link CameraMetadataNative} object to get the value from.
+     * @param key the {@link CameraMetadataNative.Key} to look up.
+     * @param <T> the type of the value.
+     * @return the value for a given {@link CameraMetadataNative.Key}.
+     */
+    public <T> T getValue(CameraMetadataNative metadata, CameraMetadataNative.Key<T> key);
+}
diff --git a/core/java/android/hardware/camera2/impl/SetCommand.java b/core/java/android/hardware/camera2/impl/SetCommand.java
new file mode 100644
index 0000000..82a01b2
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/SetCommand.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.impl;
+
+/**
+ * Setter interface for use with Command pattern metadata value setters.
+ */
+public interface SetCommand {
+
+    /**
+     * Set the value in the given metadata.
+     *
+     * @param metadata {@link CameraMetadataNative} to set value in.
+     * @param value value to set.
+     * @param <T> type of the value to set.
+     */
+    public <T> void setValue(/*inout*/CameraMetadataNative metadata,
+                             T value);
+}
diff --git a/core/java/android/hardware/camera2/legacy/BurstHolder.java b/core/java/android/hardware/camera2/legacy/BurstHolder.java
index e35eb50..c141c51 100644
--- a/core/java/android/hardware/camera2/legacy/BurstHolder.java
+++ b/core/java/android/hardware/camera2/legacy/BurstHolder.java
@@ -17,6 +17,9 @@
 package android.hardware.camera2.legacy;
 
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.util.Log;
+import android.view.Surface;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -25,8 +28,8 @@
  * Immutable container for a burst of capture results.
  */
 public class BurstHolder {
-
-    private final ArrayList<CaptureRequest> mRequests;
+    private static final String TAG = "BurstHolder";
+    private final ArrayList<RequestHolder.Builder> mRequestBuilders;
     private final boolean mRepeating;
     private final int mRequestId;
 
@@ -38,7 +41,13 @@
      * @param requests a {@link java.util.List} of {@link CaptureRequest}s in this burst.
      */
     public BurstHolder(int requestId, boolean repeating, List<CaptureRequest> requests) {
-        mRequests = new ArrayList<CaptureRequest>(requests);
+        mRequestBuilders = new ArrayList<RequestHolder.Builder>();
+        int i = 0;
+        for (CaptureRequest r : requests) {
+            mRequestBuilders.add(new RequestHolder.Builder(requestId, /*subsequenceId*/i,
+                    /*request*/r, repeating));
+            ++i;
+        }
         mRepeating = repeating;
         mRequestId = requestId;
     }
@@ -61,7 +70,7 @@
      * Return the number of requests in this burst sequence.
      */
     public int getNumberOfRequests() {
-        return mRequests.size();
+        return mRequestBuilders.size();
     }
 
     /**
@@ -73,8 +82,8 @@
     public List<RequestHolder> produceRequestHolders(long frameNumber) {
         ArrayList<RequestHolder> holders = new ArrayList<RequestHolder>();
         int i = 0;
-        for (CaptureRequest r : mRequests) {
-            holders.add(new RequestHolder(mRequestId, i, r, mRepeating, frameNumber + i));
+        for (RequestHolder.Builder b : mRequestBuilders) {
+            holders.add(b.build(frameNumber + i));
             ++i;
         }
         return holders;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
index a10a2af..88f95e1 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
@@ -28,15 +28,12 @@
 import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
 import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.utils.ListUtils;
-import android.hardware.camera2.utils.ParamsUtils;
 import android.util.Log;
-import android.util.Rational;
 import android.util.Size;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.internal.util.Preconditions.*;
 import static android.hardware.camera2.CaptureResult.*;
 
 /**
@@ -46,6 +43,36 @@
     private static final String TAG = "LegacyResultMapper";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
+    private LegacyRequest mCachedRequest = null;
+    private CameraMetadataNative mCachedResult = null;
+
+    /**
+     * Generate capture result metadata from the legacy camera request.
+     *
+     * <p>This method caches and reuses the result from the previous call to this method if
+     * the {@code parameters} of the subsequent {@link LegacyRequest} passed to this method
+     * have not changed.</p>
+     *
+     * @param legacyRequest a non-{@code null} legacy request containing the latest parameters
+     * @param timestamp the timestamp to use for this result in nanoseconds.
+     *
+     * @return {@link CameraMetadataNative} object containing result metadata.
+     */
+    public CameraMetadataNative cachedConvertResultMetadata(
+            LegacyRequest legacyRequest, long timestamp) {
+        if (mCachedRequest != null && legacyRequest.parameters.same(mCachedRequest.parameters)) {
+            CameraMetadataNative newResult = new CameraMetadataNative(mCachedResult);
+
+            // sensor.timestamp
+            newResult.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);
+            return newResult;
+        }
+
+        mCachedRequest = legacyRequest;
+        mCachedResult = convertResultMetadata(mCachedRequest, timestamp);
+        return mCachedResult;
+    }
+
     /**
      * Generate capture result metadata from the legacy camera request.
      *
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index e674736..9f27093 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -23,6 +23,8 @@
 
 import java.util.Collection;
 
+import static com.android.internal.util.Preconditions.*;
+
 /**
  * Immutable container for a single capture request and associated information.
  */
@@ -34,14 +36,131 @@
     private final int mRequestId;
     private final int mSubsequeceId;
     private final long mFrameNumber;
+    private final boolean mHasJpegTargets;
+    private final boolean mHasPreviewTargets;
 
-    RequestHolder(int requestId, int subsequenceId, CaptureRequest request, boolean repeating,
-                  long frameNumber) {
+    /**
+     * Returns true if the given surface requires jpeg buffers.
+     *
+     * @param s a {@link android.view.Surface} to check.
+     * @return true if the surface requires a jpeg buffer.
+     */
+    public static boolean jpegType(Surface s)
+            throws LegacyExceptionUtils.BufferQueueAbandonedException {
+        return LegacyCameraDevice.detectSurfaceType(s) ==
+                CameraMetadataNative.NATIVE_JPEG_FORMAT;
+    }
+
+    /**
+     * Returns true if the given surface requires non-jpeg buffer types.
+     *
+     * <p>
+     * "Jpeg buffer" refers to the buffers returned in the jpeg
+     * {@link android.hardware.Camera.PictureCallback}.  Non-jpeg buffers are created using a tee
+     * of the preview stream drawn to the surface
+     * set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
+     * equivalent methods.
+     * </p>
+     * @param s a {@link android.view.Surface} to check.
+     * @return true if the surface requires a non-jpeg buffer type.
+     */
+    public static boolean previewType(Surface s)
+            throws LegacyExceptionUtils.BufferQueueAbandonedException {
+        return LegacyCameraDevice.detectSurfaceType(s) !=
+                CameraMetadataNative.NATIVE_JPEG_FORMAT;
+    }
+
+    /**
+     * Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
+     */
+    private static boolean requestContainsJpegTargets(CaptureRequest request) {
+        for (Surface s : request.getTargets()) {
+            try {
+                if (jpegType(s)) {
+                    return true;
+                }
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if any of the surfaces targeted by the contained request require a
+     * non-jpeg buffer type.
+     */
+    private static boolean requestContainsPreviewTargets(CaptureRequest request) {
+        for (Surface s : request.getTargets()) {
+            try {
+                if (previewType(s)) {
+                    return true;
+                }
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * A builder class for {@link RequestHolder} objects.
+     *
+     * <p>
+     * This allows per-request queries to be cached for repeating {@link CaptureRequest} objects.
+     * </p>
+     */
+    public final static class Builder {
+        private final int mRequestId;
+        private final int mSubsequenceId;
+        private final CaptureRequest mRequest;
+        private final boolean mRepeating;
+        private final boolean mHasJpegTargets;
+        private final boolean mHasPreviewTargets;
+
+        /**
+         * Construct a new {@link Builder} to generate {@link RequestHolder} objects.
+         *
+         * @param requestId the ID to set in {@link RequestHolder} objects.
+         * @param subsequenceId the sequence ID to set in {@link RequestHolder} objects.
+         * @param request the original {@link CaptureRequest} to set in {@link RequestHolder}
+         *                objects.
+         * @param repeating {@code true} if the request is repeating.
+         */
+        public Builder(int requestId, int subsequenceId, CaptureRequest request,
+                       boolean repeating) {
+            checkNotNull(request, "request must not be null");
+            mRequestId = requestId;
+            mSubsequenceId = subsequenceId;
+            mRequest = request;
+            mRepeating = repeating;
+            mHasJpegTargets = requestContainsJpegTargets(mRequest);
+            mHasPreviewTargets = requestContainsPreviewTargets(mRequest);
+        }
+
+        /**
+         * Build a new {@link RequestHolder} using with parameters generated from this
+         *      {@link Builder}.
+         *
+         * @param frameNumber the {@code framenumber} to generate in the {@link RequestHolder}.
+         * @return a {@link RequestHolder} constructed with the {@link Builder}'s parameters.
+         */
+        public RequestHolder build(long frameNumber) {
+            return new RequestHolder(mRequestId, mSubsequenceId, mRequest, mRepeating, frameNumber,
+                    mHasJpegTargets, mHasPreviewTargets);
+        }
+    }
+
+    private RequestHolder(int requestId, int subsequenceId, CaptureRequest request,
+                          boolean repeating, long frameNumber, boolean hasJpegTargets,
+                          boolean hasPreviewTargets) {
         mRepeating = repeating;
         mRequest = request;
         mRequestId = requestId;
         mSubsequeceId = subsequenceId;
         mFrameNumber = frameNumber;
+        mHasJpegTargets = hasJpegTargets;
+        mHasPreviewTargets = hasPreviewTargets;
     }
 
     /**
@@ -90,86 +209,15 @@
      * Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
      */
     public boolean hasJpegTargets() {
-        for (Surface s : getHolderTargets()) {
-            try {
-                if (jpegType(s)) {
-                    return true;
-                }
-            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
-                Log.w(TAG, "Surface abandoned, skipping...", e);
-            }
-        }
-        return false;
+        return mHasJpegTargets;
     }
 
     /**
      * Returns true if any of the surfaces targeted by the contained request require a
      * non-jpeg buffer type.
      */
-    public boolean hasPreviewTargets() {
-        for (Surface s : getHolderTargets()) {
-            try {
-                if (previewType(s)) {
-                    return true;
-                }
-            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
-                Log.w(TAG, "Surface abandoned, skipping...", e);
-            }
-        }
-        return false;
+    public boolean hasPreviewTargets(){
+        return mHasPreviewTargets;
     }
 
-    /**
-     * Return the first surface targeted by the contained request that requires a
-     * non-jpeg buffer type.
-     */
-    public Surface getFirstPreviewTarget() {
-        for (Surface s : getHolderTargets()) {
-            try {
-                if (previewType(s)) {
-                    return s;
-                }
-            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
-                Log.w(TAG, "Surface abandoned, skipping...", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns true if the given surface requires jpeg buffers.
-     *
-     * @param s a {@link Surface} to check.
-     * @return true if the surface requires a jpeg buffer.
-     */
-    public static boolean jpegType(Surface s)
-            throws LegacyExceptionUtils.BufferQueueAbandonedException {
-        if (LegacyCameraDevice.detectSurfaceType(s) ==
-                CameraMetadataNative.NATIVE_JPEG_FORMAT) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if the given surface requires non-jpeg buffer types.
-     *
-     * <p>
-     * "Jpeg buffer" refers to the buffers returned in the jpeg
-     * {@link android.hardware.Camera.PictureCallback}.  Non-jpeg buffers are created using a tee
-     * of the preview stream drawn to the surface
-     * set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
-     * equivalent methods.
-     * </p>
-     * @param s a {@link Surface} to check.
-     * @return true if the surface requires a non-jpeg buffer type.
-     */
-    public static boolean previewType(Surface s)
-            throws LegacyExceptionUtils.BufferQueueAbandonedException {
-        if (LegacyCameraDevice.detectSurfaceType(s) !=
-                CameraMetadataNative.NATIVE_JPEG_FORMAT) {
-            return true;
-        }
-        return false;
-    }
 }
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 7d1be3b..cc7a90e 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -207,15 +207,17 @@
                 @Override
                 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                     RequestHolder holder = mInFlightPreview;
+
+                    if (DEBUG) {
+                        mPrevCounter.countAndLog();
+                    }
+
                     if (holder == null) {
                         mGLThreadManager.queueNewFrame(null);
                         Log.w(TAG, "Dropping preview frame.");
                         return;
                     }
 
-                    if (DEBUG) {
-                        mPrevCounter.countAndLog();
-                    }
                     mInFlightPreview = null;
 
                     if (holder.hasPreviewTargets()) {
@@ -278,7 +280,6 @@
         startPreview();
     }
 
-
     private void configureOutputs(Collection<Surface> outputs) throws IOException {
         stopPreview();
         if (mGLThreadManager != null) {
@@ -395,6 +396,7 @@
             mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
         }
 
+        mCamera.setParameters(mParams);
         // TODO: configure the JPEG surface with some arbitrary size
         // using LegacyCameraDevice.nativeConfigureSurface
     }
@@ -530,6 +532,7 @@
 
     private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
         private boolean mCleanup = false;
+        private LegacyResultMapper mMapper = new LegacyResultMapper();
 
         @Override
         public boolean handleMessage(Message msg) {
@@ -540,6 +543,10 @@
             if (DEBUG) {
                 Log.d(TAG, "Request thread handling message:" + msg.what);
             }
+            long startTime = 0;
+            if (DEBUG) {
+                startTime = SystemClock.elapsedRealtimeNanos();
+            }
             switch (msg.what) {
                 case MSG_CONFIGURE_OUTPUTS:
                     ConfigureHolder config = (ConfigureHolder) msg.obj;
@@ -553,6 +560,10 @@
                         throw new IOError(e);
                     }
                     config.condition.open();
+                    if (DEBUG) {
+                        long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
+                        Log.d(TAG, "Configure took " + totalTime + " ns");
+                    }
                     break;
                 case MSG_SUBMIT_CAPTURE_REQUEST:
                     Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
@@ -573,6 +584,8 @@
                             nextBurst.first.produceRequestHolders(nextBurst.second);
                     for (RequestHolder holder : requests) {
                         CaptureRequest request = holder.getRequest();
+
+                        boolean paramsChanged = false;
                         if (mLastRequest == null || mLastRequest.captureRequest != request) {
 
                             // The intermediate buffer is sometimes null, but we always need
@@ -587,9 +600,13 @@
                             // Parameters are mutated as a side-effect
                             LegacyMetadataMapper.convertRequestMetadata(/*inout*/legacyRequest);
 
-                            mParams = legacyRequest.parameters;
-                            mCamera.setParameters(mParams);
+                            if (!mParams.same(legacyRequest.parameters)) {
+                                mParams = legacyRequest.parameters;
+                                mCamera.setParameters(mParams);
+                                paramsChanged = true;
+                            }
                         }
+
                         mDeviceState.setCaptureStart(holder);
                         long timestamp = 0;
                         try {
@@ -616,16 +633,29 @@
                             // TODO: err handling
                             throw new IOError(e);
                         }
+
                         if (timestamp == 0) {
                             timestamp = SystemClock.elapsedRealtimeNanos();
                         }
-                        // Update parameters to the latest that we think the camera is using
-                        mLastRequest.setParameters(mCamera.getParameters());
-                        CameraMetadataNative result =
-                                LegacyResultMapper.convertResultMetadata(mLastRequest, timestamp);
+
+                        if (paramsChanged) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Params changed -- getting new Parameters from HAL.");
+                            }
+                            mParams = mCamera.getParameters();
+
+                            // Update parameters to the latest that we think the camera is using
+                            mLastRequest.setParameters(mParams);
+                        }
+
+
+                        CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
+                                mLastRequest, timestamp);
                         mDeviceState.setCaptureResult(holder, result);
                     }
                     if (DEBUG) {
+                        long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
+                        Log.d(TAG, "Capture request took " + totalTime + " ns");
                         mRequestCounter.countAndLog();
                     }
                     break;
diff --git a/core/java/android/hardware/camera2/marshal/MarshalRegistry.java b/core/java/android/hardware/camera2/marshal/MarshalRegistry.java
index 92d9057..ba821e4 100644
--- a/core/java/android/hardware/camera2/marshal/MarshalRegistry.java
+++ b/core/java/android/hardware/camera2/marshal/MarshalRegistry.java
@@ -81,17 +81,18 @@
                     break;
                 }
             }
-        }
 
-        if (marshaler == null) {
-            throw new UnsupportedOperationException(
-                     "Could not find marshaler that matches the requested " +
-                     "combination of type reference " +
-                     typeToken + " and native type " +
-                     MarshalHelpers.toStringNativeType(nativeType));
-        }
+            if (marshaler == null) {
+                throw new UnsupportedOperationException(
+                        "Could not find marshaler that matches the requested " +
+                                "combination of type reference " +
+                                typeToken + " and native type " +
+                                MarshalHelpers.toStringNativeType(nativeType));
+            }
 
-        sMarshalerMap.put(marshalToken, marshaler);
+            // Only put when no cached version exists to avoid +0.5ms lookup per call.
+            sMarshalerMap.put(marshalToken, marshaler);
+        }
 
         return marshaler;
     }
@@ -100,10 +101,12 @@
         public MarshalToken(TypeReference<T> typeReference, int nativeType) {
             this.typeReference = typeReference;
             this.nativeType = nativeType;
+            this.hash = typeReference.hashCode() ^ nativeType;
         }
 
         final TypeReference<T> typeReference;
         final int nativeType;
+        private final int hash;
 
         @Override
         public boolean equals(Object other) {
@@ -118,7 +121,7 @@
 
         @Override
         public int hashCode() {
-            return typeReference.hashCode() ^ nativeType;
+            return hash;
         }
     }
 
diff --git a/core/java/android/hardware/camera2/utils/CloseableLock.java b/core/java/android/hardware/camera2/utils/CloseableLock.java
index af55055..9ac89c8 100644
--- a/core/java/android/hardware/camera2/utils/CloseableLock.java
+++ b/core/java/android/hardware/camera2/utils/CloseableLock.java
@@ -110,7 +110,9 @@
     @Override
     public void close() {
         if (mClosed) {
-            log("close - already closed; ignoring");
+            if (VERBOSE) {
+                log("close - already closed; ignoring");
+            }
             return;
         }
 
@@ -139,7 +141,9 @@
             mLock.unlock();
         }
 
-        log("close - completed");
+        if (VERBOSE) {
+            log("close - completed");
+        }
     }
 
     /**
@@ -169,7 +173,9 @@
 
             // Lock is already closed, all further acquisitions will fail
             if (mClosed) {
-                log("acquire lock early aborted (already closed)");
+                if (VERBOSE) {
+                    log("acquire lock early aborted (already closed)");
+                }
                 return null;
             }
 
@@ -187,7 +193,9 @@
 
                 // Did another thread #close while we were waiting? Unblock immediately.
                 if (mClosed) {
-                    log("acquire lock unblocked aborted (already closed)");
+                    if (VERBOSE) {
+                        log("acquire lock unblocked aborted (already closed)");
+                    }
                     return null;
                 }
             }
@@ -200,7 +208,9 @@
             mLock.unlock();
         }
 
-        log("acquired lock (local own count = " + ownedLocks + "");
+        if (VERBOSE) {
+            log("acquired lock (local own count = " + ownedLocks + ")");
+        }
         return new ScopedLock();
     }
 
@@ -231,7 +241,9 @@
 
             // Lock is already closed, all further acquisitions will fail
             if (mClosed) {
-                log("acquire exclusive lock early aborted (already closed)");
+                if (VERBOSE) {
+                    log("acquire exclusive lock early aborted (already closed)");
+                }
                 return null;
             }
 
@@ -254,7 +266,9 @@
 
              // Did another thread #close while we were waiting? Unblock immediately.
                 if (mClosed) {
-                    log("acquire exclusive lock unblocked aborted (already closed)");
+                    if (VERBOSE) {
+                        log("acquire exclusive lock unblocked aborted (already closed)");
+                    }
                     return null;
                 }
             }
@@ -267,7 +281,9 @@
             mLock.unlock();
         }
 
-        log("acquired exclusive lock (local own count = " + ownedLocks + "");
+        if (VERBOSE) {
+            log("acquired exclusive lock (local own count = " + ownedLocks + ")");
+        }
         return new ScopedLock();
     }
 
@@ -318,13 +334,13 @@
             mLock.unlock();
         }
 
-        log("released lock (local lock count " + ownedLocks + ")");
+        if (VERBOSE) {
+             log("released lock (local lock count " + ownedLocks + ")");
+        }
     }
 
     private void log(String what) {
-        if (VERBOSE) {
-            Log.v(TAG + "[" + mName + "]", what);
-        }
+        Log.v(TAG + "[" + mName + "]", what);
     }
 
 }
diff --git a/core/java/android/hardware/camera2/utils/TypeReference.java b/core/java/android/hardware/camera2/utils/TypeReference.java
index d0c919c..24ce124 100644
--- a/core/java/android/hardware/camera2/utils/TypeReference.java
+++ b/core/java/android/hardware/camera2/utils/TypeReference.java
@@ -46,6 +46,7 @@
  */
 public abstract class TypeReference<T> {
     private final Type mType;
+    private final int mHash;
 
     /**
      * Create a new type reference for {@code T}.
@@ -73,6 +74,7 @@
             throw new IllegalArgumentException(
                     "Including a type variable in a type reference is not allowed");
         }
+        mHash = mType.hashCode();
     }
 
     /**
@@ -84,11 +86,11 @@
 
     private TypeReference(Type type) {
         mType = type;
-
         if (containsTypeVariable(mType)) {
             throw new IllegalArgumentException(
                     "Including a type variable in a type reference is not allowed");
         }
+        mHash = mType.hashCode();
     }
 
     private static class SpecializedTypeReference<T> extends TypeReference<T> {
@@ -249,7 +251,7 @@
      */
     @Override
     public int hashCode() {
-        return mType.hashCode();
+        return mHash;
     }
 
     /**
diff --git a/core/java/android/hardware/soundtrigger/Keyphrase.java b/core/java/android/hardware/soundtrigger/Keyphrase.java
index 42fd350..51311bb 100644
--- a/core/java/android/hardware/soundtrigger/Keyphrase.java
+++ b/core/java/android/hardware/soundtrigger/Keyphrase.java
@@ -30,7 +30,11 @@
     /** A hint text to display corresponding to this keyphrase, e.g. "Hello There". */
     public final String hintText;
     /** The locale of interest when using this Keyphrase. */
-    public String locale;
+    public final String locale;
+    /** The various recognition modes supported by this keyphrase */
+    public final int recognitionModeFlags;
+    /** The users associated with this keyphrase */
+    public final int[] users;
 
     public static final Parcelable.Creator<Keyphrase> CREATOR
             = new Parcelable.Creator<Keyphrase>() {
@@ -44,13 +48,26 @@
     };
 
     private static Keyphrase fromParcel(Parcel in) {
-        return new Keyphrase(in.readInt(), in.readString(), in.readString());
+        int id = in.readInt();
+        String hintText = in.readString();
+        String locale = in.readString();
+        int recognitionModeFlags = in.readInt();
+        int numUsers = in.readInt();
+        int[] users = null;
+        if (numUsers > 0) {
+            users = new int[numUsers];
+            in.readIntArray(users);
+        }
+        return new Keyphrase(id, hintText, locale, recognitionModeFlags, users);
     }
 
-    public Keyphrase(int id, String hintText, String locale) {
+    public Keyphrase(int id, String hintText, String locale, int recognitionModeFlags,
+            int[] users) {
         this.id = id;
         this.hintText = hintText;
         this.locale = locale;
+        this.recognitionModeFlags = recognitionModeFlags;
+        this.users = users;
     }
 
     @Override
@@ -58,6 +75,13 @@
         dest.writeInt(id);
         dest.writeString(hintText);
         dest.writeString(locale);
+        dest.writeInt(recognitionModeFlags);
+        if (users != null) {
+            dest.writeInt(users.length);
+            dest.writeIntArray(users);
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     @Override
@@ -98,4 +122,14 @@
             return false;
         return true;
     }
+
+    @Override
+    public String toString() {
+        return "Keyphrase[id=" + id + ", text=" + hintText + ", locale=" + locale
+                + ", recognitionModes=" + recognitionModeFlags + "]";
+    }
+
+    protected SoundTrigger.Keyphrase convertToSoundTriggerKeyphrase() {
+        return new SoundTrigger.Keyphrase(id, recognitionModeFlags, locale, hintText, users);
+    }
 }
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index 2f5de6a..c74134a 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -239,7 +239,8 @@
      * @param keyphrase The keyphrase that the user needs to be enrolled to.
      * @param locale The locale for which the enrollment needs to be performed.
      *        This is a Java locale, for example "en_US".
-     * @return true, if an enrollment client supports the given keyphrase and the given locale.
+     * @return The metadata, if an enrollment client supports the given keyphrase
+     *         and the given locale, null otherwise.
      */
     public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) {
         if (mKeyphrases == null || mKeyphrases.length == 0) {
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java
index 4ddba6a..a5ab0d2 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java
@@ -65,4 +65,15 @@
         }
         dest.writeParcelableArray(keyphrases, 0);
     }
+
+    public SoundTrigger.KeyphraseSoundModel convertToSoundTriggerKeyphraseSoundModel() {
+        SoundTrigger.Keyphrase[] stKeyphrases = null;
+        if (keyphrases != null) {
+            stKeyphrases = new SoundTrigger.Keyphrase[keyphrases.length];
+            for (int i = 0; i < keyphrases.length; i++) {
+                stKeyphrases[i] = keyphrases[i].convertToSoundTriggerKeyphrase();
+            }
+        }
+        return new SoundTrigger.KeyphraseSoundModel(uuid, data, stKeyphrases);
+    }
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java b/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java
index 0be068d..3659621 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java
@@ -17,6 +17,7 @@
 package android.hardware.soundtrigger;
 
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -56,7 +57,7 @@
     private final ModuleProperties mModuleProperties;
     private final SoundTriggerModule mModule;
 
-    private final SparseArray<Listener> mListeners;
+    private final SparseArray<Listener> mActiveListeners;
 
     private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
 
@@ -77,7 +78,7 @@
     public SoundTriggerHelper() {
         ArrayList <ModuleProperties> modules = new ArrayList<>();
         int status = SoundTrigger.listModules(modules);
-        mListeners = new SparseArray<>(1);
+        mActiveListeners = new SparseArray<>(1);
         if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
             // TODO: Figure out how to handle errors in listing the modules here.
             dspInfo = null;
@@ -94,27 +95,9 @@
     }
 
     /**
-     * @return True, if the given {@link Keyphrase} is supported on DSP.
-     */
-    public boolean isKeyphraseSupported(Keyphrase keyphrase) {
-        // TODO: We also need to look into a SoundTrigger API that let's us
-        // query this. For now just return true.
-        return true;
-    }
-
-    /**
-     * @return True, if the given {@link Keyphrase} has been enrolled.
-     */
-    public boolean isKeyphraseEnrolled(Keyphrase keyphrase) {
-        // TODO: Query VoiceInteractionManagerService
-        // to list registered sound models.
-        return false;
-    }
-
-    /**
      * @return True, if a recognition for the given {@link Keyphrase} is active.
      */
-    public boolean isKeyphraseActive(Keyphrase keyphrase) {
+    public synchronized boolean isKeyphraseActive(Keyphrase keyphrase) {
         // TODO: Check if the recognition for the keyphrase is currently active.
         return false;
     }
@@ -124,55 +107,98 @@
      *
      * @param keyphraseId The identifier of the keyphrase for which
      *        the recognition is to be started.
+     * @param soundModel The sound model to use for recognition.
      * @param listener The listener for the recognition events related to the given keyphrase.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    public int startRecognition(int keyphraseId, Listener listener) {
+    public synchronized int startRecognition(int keyphraseId,
+            SoundTrigger.KeyphraseSoundModel soundModel,
+            Listener listener, RecognitionConfig recognitionConfig) {
         if (dspInfo == null || mModule == null) {
             Slog.w(TAG, "Attempting startRecognition without the capability");
             return STATUS_ERROR;
         }
 
-        if (mListeners.get(keyphraseId) != listener) {
+        Listener oldListener = mActiveListeners.get(keyphraseId);
+        if (oldListener != null && oldListener != listener) {
             if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) {
                 Slog.w(TAG, "Canceling previous recognition");
                 // TODO: Inspect the return codes here.
                 mModule.unloadSoundModel(mCurrentSoundModelHandle);
             }
-            mListeners.get(keyphraseId).onListeningStateChanged(STATE_STOPPED);
+            mActiveListeners.get(keyphraseId).onListeningStateChanged(STATE_STOPPED);
+            mActiveListeners.remove(keyphraseId);
         }
 
+        int[] handle = new int[] { INVALID_SOUND_MODEL_HANDLE };
+        int status = mModule.loadSoundModel(soundModel, handle);
+        if (status != SoundTrigger.STATUS_OK) {
+            Slog.w(TAG, "loadSoundModel call failed with " + status);
+            return STATUS_ERROR;
+        }
+        if (handle[0] == INVALID_SOUND_MODEL_HANDLE) {
+            Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+            return STATUS_ERROR;
+        }
+
+        // Start the recognition.
+        status = mModule.startRecognition(handle[0], recognitionConfig);
+        if (status != SoundTrigger.STATUS_OK) {
+            Slog.w(TAG, "startRecognition failed with " + status);
+            return STATUS_ERROR;
+        }
+
+        // Everything went well!
+        mCurrentSoundModelHandle = handle[0];
         // Register the new listener. This replaces the old one.
         // There can only be a maximum of one active listener for a keyphrase
         // at any given time.
-        mListeners.put(keyphraseId, listener);
-        // TODO: Get the sound model for the given keyphrase here.
-        // mModule.loadSoundModel(model, soundModelHandle);
-        // mModule.startRecognition(soundModelHandle, data);
-        // mCurrentSoundModelHandle = soundModelHandle;
-        return STATUS_ERROR;
+        mActiveListeners.put(keyphraseId, listener);
+        return STATUS_OK;
     }
 
     /**
      * Stops recognition for the given {@link Keyphrase} if a recognition is currently active.
      *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        the recognition is to be stopped.
+     * @param listener The listener for the recognition events related to the given keyphrase.
+     *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    public int stopRecognition(int id, Listener listener) {
+    public synchronized int stopRecognition(int keyphraseId, Listener listener) {
         if (dspInfo == null || mModule == null) {
             Slog.w(TAG, "Attempting stopRecognition without the capability");
             return STATUS_ERROR;
         }
 
-        if (mListeners.get(id) != listener) {
+        Listener currentListener = mActiveListeners.get(keyphraseId);
+        if (currentListener == null) {
+            // startRecognition hasn't been called or it failed.
+            Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+            return STATUS_ERROR;
+        } else if (currentListener != listener) {
+            // TODO: Figure out if this should match the listener that was passed in during
+            // startRecognition, or should we allow a different listener to stop the recognition,
+            // in which case we don't need to pass in a listener here.
             Slog.w(TAG, "Attempting stopRecognition for another recognition");
             return STATUS_ERROR;
         } else {
             // Stop recognition if it's the current one, ignore otherwise.
             // TODO: Inspect the return codes here.
-            mModule.stopRecognition(mCurrentSoundModelHandle);
-            mModule.unloadSoundModel(mCurrentSoundModelHandle);
+            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "stopRecognition call failed with " + status);
+                return STATUS_ERROR;
+            }
+            status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "unloadSoundModel call failed with " + status);
+                return STATUS_ERROR;
+            }
+
             mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
+            mActiveListeners.remove(keyphraseId);
             return STATUS_OK;
         }
     }
@@ -184,28 +210,26 @@
         // TODO: Get the keyphrase out of the event and fire events on it.
         // For now, as a nasty workaround, we fire all events to the listener for
         // keyphrase with TEMP_KEYPHRASE_ID.
+        Listener listener = null;
+        synchronized(this) {
+            // TODO: The keyphrase should come from the recognition event
+            // as it may be for a different keyphrase than the current one.
+            listener = mActiveListeners.get(TEMP_KEYPHRASE_ID);
+        }
+        if (listener == null) {
+            Slog.w(TAG, "received onRecognition event without any listener for it");
+            return;
+        }
 
         switch (event.status) {
             case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
-                // TODO: The keyphrase should come from the recognition event
-                // as it may be for a different keyphrase than the current one.
-                if (mListeners.get(TEMP_KEYPHRASE_ID) != null) {
-                    mListeners.get(TEMP_KEYPHRASE_ID).onKeyphraseSpoken();
-                }
+                listener.onKeyphraseSpoken();
                 break;
             case SoundTrigger.RECOGNITION_STATUS_ABORT:
-                // TODO: The keyphrase should come from the recognition event
-                // as it may be for a different keyphrase than the current one.
-                if (mListeners.get(TEMP_KEYPHRASE_ID) != null) {
-                    mListeners.get(TEMP_KEYPHRASE_ID).onListeningStateChanged(STATE_STOPPED);
-                }
+                listener.onListeningStateChanged(STATE_STOPPED);
                 break;
             case SoundTrigger.RECOGNITION_STATUS_FAILURE:
-                // TODO: The keyphrase should come from the recognition event
-                // as it may be for a different keyphrase than the current one.
-                if (mListeners.get(TEMP_KEYPHRASE_ID) != null) {
-                    mListeners.get(TEMP_KEYPHRASE_ID).onListeningStateChanged(STATE_STOPPED);
-                }
+                listener.onListeningStateChanged(STATE_STOPPED);
                 break;
         }
     }
diff --git a/core/java/android/net/PSKKeyManager.java b/core/java/android/net/PSKKeyManager.java
index 92dd141..e868c4f 100644
--- a/core/java/android/net/PSKKeyManager.java
+++ b/core/java/android/net/PSKKeyManager.java
@@ -81,19 +81,16 @@
  * TLS-PSK in {@code SSLSocket}, {@code SSLServerSocket} and {@code SSLEngine} instances obtained
  * from it.
  * <pre> {@code
- *  PSKKeyManager myPskKeyManager = ...;
+ * PSKKeyManager myPskKeyManager = ...;
  *
  * SSLContext sslContext = SSLContext.getInstance("TLS");
  * sslContext.init(
  *         new KeyManager[] &#123;myPskKeyManager&#125;,
- *         new TrustManager[0], // No TrustManagers needed in TLS-PSK
+ *         new TrustManager[0], // No TrustManagers needed for 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 {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c22c4b6..aab7b1f 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -120,6 +120,16 @@
     public static final int PROCESS_STATE = 12;
 
     /**
+     * A constant indicating a sync timer
+     */
+    public static final int SYNC = 13;
+
+    /**
+     * A constant indicating a job timer
+     */
+    public static final int JOB = 14;
+
+    /**
      * Include all of the data in the stats, including previously saved data.
      */
     public static final int STATS_SINCE_CHARGED = 0;
@@ -142,7 +152,7 @@
     /**
      * Bump the version on this if the checkin format changes.
      */
-    private static final int BATTERY_STATS_CHECKIN_VERSION = 8;
+    private static final int BATTERY_STATS_CHECKIN_VERSION = 9;
     
     private static final long BYTES_PER_KB = 1024;
     private static final long BYTES_PER_MB = 1048576; // 1024^2
@@ -157,6 +167,8 @@
     private static final String FOREGROUND_DATA = "fg";
     private static final String STATE_TIME_DATA = "st";
     private static final String WAKELOCK_DATA = "wl";
+    private static final String SYNC_DATA = "sy";
+    private static final String JOB_DATA = "jb";
     private static final String KERNEL_WAKELOCK_DATA = "kwl";
     private static final String WAKEUP_REASON_DATA = "wr";
     private static final String NETWORK_DATA = "nt";
@@ -273,6 +285,20 @@
         public abstract Map<String, ? extends Wakelock> getWakelockStats();
 
         /**
+         * Returns a mapping containing sync statistics.
+         *
+         * @return a Map from Strings to Timer objects.
+         */
+        public abstract Map<String, ? extends Timer> getSyncStats();
+
+        /**
+         * Returns a mapping containing scheduled job statistics.
+         *
+         * @return a Map from Strings to Timer objects.
+         */
+        public abstract Map<String, ? extends Timer> getJobStats();
+
+        /**
          * The statistics associated with a particular wake lock.
          */
         public static abstract class Wakelock {
@@ -660,13 +686,19 @@
         public static final int EVENT_FOREGROUND = 0x0002;
         // Event is about an application package that is at the top of the screen.
         public static final int EVENT_TOP = 0x0003;
-        // Event is about an application package that is at the top of the screen.
+        // Event is about active sync operations.
         public static final int EVENT_SYNC = 0x0004;
         // Events for all additional wake locks aquired/release within a wake block.
         // These are not generated by default.
         public static final int EVENT_WAKE_LOCK = 0x0005;
+        // Event is about an application executing a scheduled job.
+        public static final int EVENT_JOB = 0x0006;
+        // Events for users running.
+        public static final int EVENT_USER_RUNNING = 0x0007;
+        // Events for foreground user.
+        public static final int EVENT_USER_FOREGROUND = 0x0008;
         // Number of event types.
-        public static final int EVENT_COUNT = 0x0006;
+        public static final int EVENT_COUNT = 0x0009;
         // Mask to extract out only the type part of the event.
         public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
 
@@ -680,6 +712,14 @@
         public static final int EVENT_SYNC_FINISH = EVENT_SYNC | EVENT_FLAG_FINISH;
         public static final int EVENT_WAKE_LOCK_START = EVENT_WAKE_LOCK | EVENT_FLAG_START;
         public static final int EVENT_WAKE_LOCK_FINISH = EVENT_WAKE_LOCK | EVENT_FLAG_FINISH;
+        public static final int EVENT_JOB_START = EVENT_JOB | EVENT_FLAG_START;
+        public static final int EVENT_JOB_FINISH = EVENT_JOB | EVENT_FLAG_FINISH;
+        public static final int EVENT_USER_RUNNING_START = EVENT_USER_RUNNING | EVENT_FLAG_START;
+        public static final int EVENT_USER_RUNNING_FINISH = EVENT_USER_RUNNING | EVENT_FLAG_FINISH;
+        public static final int EVENT_USER_FOREGROUND_START =
+                EVENT_USER_FOREGROUND | EVENT_FLAG_START;
+        public static final int EVENT_USER_FOREGROUND_FINISH =
+                EVENT_USER_FOREGROUND | EVENT_FLAG_FINISH;
 
         // For CMD_EVENT.
         public int eventCode;
@@ -1269,11 +1309,11 @@
     };
 
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
-            "null", "proc", "fg", "top", "sync", "wake_lock_in"
+            "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg"
     };
 
     public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
-            "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl"
+            "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf"
     };
 
     /**
@@ -1857,9 +1897,9 @@
                 screenOnTime / 1000, phoneOnTime / 1000, wifiOnTime / 1000,
                 wifiRunningTime / 1000, bluetoothOnTime / 1000,
                 mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes,
-                fullWakeLockTimeTotal, partialWakeLockTimeTotal,
-                0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which),
-                getMobileRadioActiveAdjustedTime(which), interactiveTime / 1000,
+                fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000,
+                0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which) / 1000,
+                getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000,
                 lowPowerModeEnabledTime / 1000);
         
         // Dump screen brightness stats
@@ -2080,10 +2120,9 @@
                 }
             }
             
-            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
+            Map<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
             if (wakelocks.size() > 0) {
-                for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
-                        : wakelocks.entrySet()) {
+                for (Map.Entry<String, ? extends Uid.Wakelock> ent : wakelocks.entrySet()) {
                     Uid.Wakelock wl = ent.getValue();
                     String linePrefix = "";
                     sb.setLength(0);
@@ -2105,6 +2144,32 @@
                 }
             }
 
+            Map<String, ? extends Timer> syncs = u.getSyncStats();
+            if (syncs.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : syncs.entrySet()) {
+                    Timer timer = ent.getValue();
+                    // Convert from microseconds to milliseconds with rounding
+                    long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                    int count = timer.getCountLocked(which);
+                    if (totalTime != 0) {
+                        dumpLine(pw, uid, category, SYNC_DATA, ent.getKey(), totalTime, count);
+                    }
+                }
+            }
+
+            Map<String, ? extends Timer> jobs = u.getJobStats();
+            if (jobs.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : jobs.entrySet()) {
+                    Timer timer = ent.getValue();
+                    // Convert from microseconds to milliseconds with rounding
+                    long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                    int count = timer.getCountLocked(which);
+                    if (totalTime != 0) {
+                        dumpLine(pw, uid, category, JOB_DATA, ent.getKey(), totalTime, count);
+                    }
+                }
+            }
+
             SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
             int NSE = sensors.size();
             for (int ise=0; ise<NSE; ise++) {
@@ -2937,8 +3002,7 @@
             if (wakelocks.size() > 0) {
                 long totalFull = 0, totalPartial = 0, totalWindow = 0;
                 int count = 0;
-                for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
-                    : wakelocks.entrySet()) {
+                for (Map.Entry<String, ? extends Uid.Wakelock> ent : wakelocks.entrySet()) {
                     Uid.Wakelock wl = ent.getValue();
                     String linePrefix = ": ";
                     sb.setLength(0);
@@ -2998,6 +3062,56 @@
                 }
             }
 
+            Map<String, ? extends Timer> syncs = u.getSyncStats();
+            if (syncs.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : syncs.entrySet()) {
+                    Timer timer = ent.getValue();
+                    // Convert from microseconds to milliseconds with rounding
+                    long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                    int count = timer.getCountLocked(which);
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    Sync ");
+                    sb.append(ent.getKey());
+                    sb.append(": ");
+                    if (totalTime != 0) {
+                        formatTimeMs(sb, totalTime);
+                        sb.append("realtime (");
+                        sb.append(count);
+                        sb.append(" times)");
+                    } else {
+                        sb.append("(not used)");
+                    }
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+
+            Map<String, ? extends Timer> jobs = u.getJobStats();
+            if (syncs.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : jobs.entrySet()) {
+                    Timer timer = ent.getValue();
+                    // Convert from microseconds to milliseconds with rounding
+                    long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                    int count = timer.getCountLocked(which);
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    Job ");
+                    sb.append(ent.getKey());
+                    sb.append(": ");
+                    if (totalTime != 0) {
+                        formatTimeMs(sb, totalTime);
+                        sb.append("realtime (");
+                        sb.append(count);
+                        sb.append(" times)");
+                    } else {
+                        sb.append("(not used)");
+                    }
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+
             SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
             int NSE = sensors.size();
             for (int ise=0; ise<NSE; ise++) {
@@ -3260,7 +3374,6 @@
         int oldTemp = -1;
         int oldVolt = -1;
         long lastTime = -1;
-        long firstTime = -1;
 
         void reset() {
             oldState = oldState2 = 0;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 93f834a..bfe90e6 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -247,57 +247,6 @@
     }
 
     /**
-     * @hide
-     */
-    public static final class Preferences {
-
-        /**
-         * A key in the {@link android.provider.Settings android.provider.Settings} provider
-         * that stores the preferred sorting order for contacts (by given name vs. by family name).
-         *
-         * @hide
-         */
-        public static final String SORT_ORDER = "android.contacts.SORT_ORDER";
-
-        /**
-         * The value for the SORT_ORDER key corresponding to sorting by given name first.
-         *
-         * @hide
-         */
-        public static final int SORT_ORDER_PRIMARY = 1;
-
-        /**
-         * The value for the SORT_ORDER key corresponding to sorting by family name first.
-         *
-         * @hide
-         */
-        public static final int SORT_ORDER_ALTERNATIVE = 2;
-
-        /**
-         * A key in the {@link android.provider.Settings android.provider.Settings} provider
-         * that stores the preferred display order for contacts (given name first vs. family
-         * name first).
-         *
-         * @hide
-         */
-        public static final String DISPLAY_ORDER = "android.contacts.DISPLAY_ORDER";
-
-        /**
-         * The value for the DISPLAY_ORDER key corresponding to showing the given name first.
-         *
-         * @hide
-         */
-        public static final int DISPLAY_ORDER_PRIMARY = 1;
-
-        /**
-         * The value for the DISPLAY_ORDER key corresponding to showing the family name first.
-         *
-         * @hide
-         */
-        public static final int DISPLAY_ORDER_ALTERNATIVE = 2;
-    }
-
-    /**
      * A Directory represents a contacts corpus, e.g. Local contacts,
      * Google Apps Global Address List or Corporate Global Address List.
      * <p>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bfb5a26..9f1279a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2496,12 +2496,6 @@
         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
          */
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 8fc3c5a..cc09653 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -76,6 +76,7 @@
 
     private static final String EXIT_CONDITION_TAG = "exitCondition";
     private static final String EXIT_CONDITION_ATT_ID = "id";
+    private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
 
     public boolean allowCalls;
     public boolean allowMessages;
@@ -89,6 +90,7 @@
     public ComponentName[] conditionComponents;
     public Uri[] conditionIds;
     public Uri exitConditionId;
+    public ComponentName exitConditionComponent;
 
     public ZenModeConfig() { }
 
@@ -114,6 +116,7 @@
         }
         allowFrom = source.readInt();
         exitConditionId = source.readParcelable(null);
+        exitConditionComponent = source.readParcelable(null);
     }
 
     @Override
@@ -144,6 +147,7 @@
         }
         dest.writeInt(allowFrom);
         dest.writeParcelable(exitConditionId, 0);
+        dest.writeParcelable(exitConditionComponent, 0);
     }
 
     @Override
@@ -160,6 +164,7 @@
             .append(",conditionIds=")
             .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
             .append(",exitConditionId=").append(exitConditionId)
+            .append(",exitConditionComponent=").append(exitConditionComponent)
             .append(']').toString();
     }
 
@@ -191,7 +196,8 @@
                 && other.sleepEndMinute == sleepEndMinute
                 && Objects.deepEquals(other.conditionComponents, conditionComponents)
                 && Objects.deepEquals(other.conditionIds, conditionIds)
-                && Objects.equals(other.exitConditionId, exitConditionId);
+                && Objects.equals(other.exitConditionId, exitConditionId)
+                && Objects.equals(other.exitConditionComponent, exitConditionComponent);
     }
 
     @Override
@@ -199,7 +205,7 @@
         return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode,
                 sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute,
                 Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
-                exitConditionId);
+                exitConditionId, exitConditionComponent);
     }
 
     public boolean isValid() {
@@ -289,6 +295,8 @@
                     }
                 } else if (EXIT_CONDITION_TAG.equals(tag)) {
                     rt.exitConditionId = safeUri(parser, EXIT_CONDITION_ATT_ID);
+                    rt.exitConditionComponent =
+                            safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
                 }
             }
         }
@@ -325,9 +333,11 @@
                 out.endTag(null, CONDITION_TAG);
             }
         }
-        if (exitConditionId != null) {
+        if (exitConditionId != null && exitConditionComponent != null) {
             out.startTag(null, EXIT_CONDITION_TAG);
             out.attribute(null, EXIT_CONDITION_ATT_ID, exitConditionId.toString());
+            out.attribute(null, EXIT_CONDITION_ATT_COMPONENT,
+                    exitConditionComponent.flattenToString());
             out.endTag(null, EXIT_CONDITION_TAG);
         }
         out.endTag(null, ZEN_TAG);
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 67ce31e..306543f 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -20,9 +20,19 @@
 import android.hardware.soundtrigger.Keyphrase;
 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
 import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTriggerHelper;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.app.IVoiceInteractionManagerService;
+
+import java.util.List;
+
 /**
  * A class that lets a VoiceInteractionService implementation interact with
  * always-on keyphrase detection APIs.
@@ -72,11 +82,22 @@
 
     private final String mText;
     private final String mLocale;
-    private final Keyphrase mKeyphrase;
+    /**
+     * The metadata of the Keyphrase, derived from the enrollment application.
+     * This may be null if this keyphrase isn't supported by the enrollment application.
+     */
+    private final KeyphraseMetadata mKeyphraseMetadata;
+    /**
+     * The sound model for the keyphrase, derived from the model management service
+     * (IVoiceInteractionManagerService). May be null if the keyphrase isn't enrolled yet.
+     */
+    private final KeyphraseSoundModel mEnrolledSoundModel;
     private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
     private final SoundTriggerHelper mSoundTriggerHelper;
     private final SoundTriggerHelper.Listener mListener;
     private final int mAvailability;
+    private final IVoiceInteractionService mVoiceInteractionService;
+    private final IVoiceInteractionManagerService mModelManagementService;
 
     private int mRecognitionState;
 
@@ -103,25 +124,30 @@
      * @param text The keyphrase text to get the detector for.
      * @param locale The java locale for the detector.
      * @param callback A non-null Callback for receiving the recognition events.
+     * @param voiceInteractionService The current voice interaction service.
+     * @param modelManagementService A service that allows management of sound models.
      *
      * @hide
      */
     public AlwaysOnHotwordDetector(String text, String locale, Callback callback,
             KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
-            SoundTriggerHelper soundTriggerHelper) {
+            SoundTriggerHelper soundTriggerHelper,
+            IVoiceInteractionService voiceInteractionService,
+            IVoiceInteractionManagerService modelManagementService) {
         mText = text;
         mLocale = locale;
         mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
-        KeyphraseMetadata keyphraseMetadata =
-                mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
-        if (keyphraseMetadata != null) {
-            mKeyphrase = new Keyphrase(keyphraseMetadata.id, text, locale);
-        } else {
-            mKeyphrase = null;
-        }
+        mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
         mListener = new SoundTriggerListener(callback);
         mSoundTriggerHelper = soundTriggerHelper;
-        mAvailability = getAvailabilityInternal();
+        mVoiceInteractionService = voiceInteractionService;
+        mModelManagementService = modelManagementService;
+        if (mKeyphraseMetadata != null) {
+            mEnrolledSoundModel = internalGetKeyphraseSoundModel(mKeyphraseMetadata.id);
+        } else {
+            mEnrolledSoundModel = null;
+        }
+        mAvailability = internalGetAvailability();
     }
 
     /**
@@ -171,7 +197,16 @@
         }
 
         mRecognitionState = RECOGNITION_REQUESTED;
-        int code = mSoundTriggerHelper.startRecognition(mKeyphrase.id, mListener);
+        mRecognitionState = RECOGNITION_REQUESTED;
+        KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
+        // TODO: Do we need to do something about the confidence level here?
+        // TODO: Read the recognition mode flag from the KeyphraseMetadata.
+        // TODO: Take in captureTriggerAudio as a method param here.
+        recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.id,
+                SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, new ConfidenceLevel[0]);
+        int code = mSoundTriggerHelper.startRecognition(mKeyphraseMetadata.id,
+                mEnrolledSoundModel.convertToSoundTriggerKeyphraseSoundModel(), mListener,
+                new RecognitionConfig(false, recognitionExtra, null /* additional data */));
         if (code != SoundTriggerHelper.STATUS_OK) {
             Slog.w(TAG, "startRecognition() failed with error code " + code);
             return STATUS_ERROR;
@@ -195,7 +230,8 @@
         }
 
         mRecognitionState = RECOGNITION_NOT_REQUESTED;
-        int code = mSoundTriggerHelper.stopRecognition(mKeyphrase.id, mListener);
+        int code = mSoundTriggerHelper.stopRecognition(mKeyphraseMetadata.id, mListener);
+
         if (code != SoundTriggerHelper.STATUS_OK) {
             Slog.w(TAG, "stopRecognition() failed with error code " + code);
             return STATUS_ERROR;
@@ -230,19 +266,51 @@
         return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale);
     }
 
-    private int getAvailabilityInternal() {
+    private int internalGetAvailability() {
+        // No DSP available
         if (mSoundTriggerHelper.dspInfo == null) {
             return KEYPHRASE_HARDWARE_UNAVAILABLE;
         }
-        if (mKeyphrase == null || !mSoundTriggerHelper.isKeyphraseSupported(mKeyphrase)) {
+        // No enrollment application supports this keyphrase/locale
+        if (mKeyphraseMetadata == null) {
             return KEYPHRASE_UNSUPPORTED;
         }
-        if (!mSoundTriggerHelper.isKeyphraseEnrolled(mKeyphrase)) {
+        // This keyphrase hasn't been enrolled.
+        if (mEnrolledSoundModel == null) {
             return KEYPHRASE_UNENROLLED;
         }
         return KEYPHRASE_ENROLLED;
     }
 
+    /**
+     * @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
+     */
+    private KeyphraseSoundModel internalGetKeyphraseSoundModel(int keyphraseId) {
+        List<KeyphraseSoundModel> soundModels;
+        try {
+            soundModels = mModelManagementService
+                    .listRegisteredKeyphraseSoundModels(mVoiceInteractionService);
+            if (soundModels == null || soundModels.isEmpty()) {
+                Slog.i(TAG, "No available sound models for keyphrase ID: " + keyphraseId);
+                return null;
+            }
+            for (KeyphraseSoundModel soundModel : soundModels) {
+                if (soundModel.keyphrases == null) {
+                    continue;
+                }
+                for (Keyphrase keyphrase : soundModel.keyphrases) {
+                    // TODO: Check the user handle here to only load a model for the current user.
+                    if (keyphrase.id == keyphraseId) {
+                        return soundModel;
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!");
+        }
+        return null;
+    }
+
     /** @hide */
     static final class SoundTriggerListener implements SoundTriggerHelper.Listener {
         private final Callback mCallback;
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index cf8d502..a9b1959 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -104,10 +104,8 @@
      */
     public final AlwaysOnHotwordDetector getAlwaysOnHotwordDetector(
             String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) {
-        // TODO: Cache instances and return the same one instead of creating a new interactor
-        // for the same keyphrase/locale combination.
         return new AlwaysOnHotwordDetector(keyphrase, locale, callback,
-                mKeyphraseEnrollmentInfo, mSoundTriggerHelper);
+                mKeyphraseEnrollmentInfo, mSoundTriggerHelper, mInterface, mSystemService);
     }
 
     /**
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index c6005b9..363f97f 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -160,7 +160,7 @@
     @Override
     public TransitionSet setDuration(long duration) {
         super.setDuration(duration);
-        if (mDuration >= 0) {
+        if (mDuration >= 0 && mTransitions != null) {
             int numTransitions = mTransitions.size();
             for (int i = 0; i < numTransitions; ++i) {
                 mTransitions.get(i).setDuration(duration);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 6cda905..3f10b92 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -75,9 +75,7 @@
     public static final int DENSITY_400 = 400;
 
     /**
-     * Standard quantized DPI for extra-extra-high-density screens.  Applications
-     * should not generally worry about this density; relying on XHIGH graphics
-     * being scaled up to it should be sufficient for almost all cases.
+     * Standard quantized DPI for extra-extra-high-density screens.
      */
     public static final int DENSITY_XXHIGH = 480;
 
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index 6aae84d..f5d515d 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -39,4 +39,20 @@
             throw new IOException(e.getMessage().substring(PREFIX_IO.length()));
         }
     }
+
+    public static String getCompleteMessage(String msg, Throwable t) {
+        final StringBuilder builder = new StringBuilder();
+        if (msg != null) {
+            builder.append(msg).append(": ");
+        }
+        builder.append(t.getMessage());
+        while ((t = t.getCause()) != null) {
+            builder.append(": ").append(t.getMessage());
+        }
+        return builder.toString();
+    }
+
+    public static String getCompleteMessage(Throwable t) {
+        return getCompleteMessage(null, t);
+    }
 }
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 26f47f9..cc090ad5 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -46,6 +46,12 @@
     public static final int CLOCK_TICK = 4;
 
     /**
+     * The user has pressed either a day or month or year date of a Calendar.
+     * @hide
+     */
+    public static final int CALENDAR_DATE = 5;
+
+    /**
      * This is a private constant.  Feel free to renumber as desired.
      * @hide
      */
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index ae59bbc..a61d771 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -80,7 +80,7 @@
     void removeWindowToken(IBinder token);
     void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
             int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
-            int configChanges, boolean voiceInteraction);
+            int configChanges, boolean voiceInteraction, boolean launchTaskBehind);
     void setAppGroupId(IBinder token, int groupId);
     void setAppOrientation(IApplicationToken token, int requestedOrientation);
     int getAppOrientation(IApplicationToken token);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 120c036..b033780 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -81,6 +81,9 @@
     // applied as translation when updating the root render node.
     private int mInsetTop, mInsetLeft;
 
+    // Whether the surface has insets. Used to protect opacity.
+    private boolean mHasInsets;
+
     // Light and shadow properties specified by the theme.
     private final float mLightY;
     private final float mLightZ;
@@ -188,12 +191,17 @@
         final float lightX = width / 2.0f;
         mWidth = width;
         mHeight = height;
-        if (surfaceInsets != null) {
+        if (surfaceInsets != null && !surfaceInsets.isEmpty()) {
+            mHasInsets = true;
             mInsetLeft = surfaceInsets.left;
             mInsetTop = surfaceInsets.top;
             mSurfaceWidth = width + mInsetLeft + surfaceInsets.right;
             mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom;
+
+            // If the surface has insets, it can't be opaque.
+            setOpaque(false);
         } else {
+            mHasInsets = false;
             mInsetLeft = 0;
             mInsetTop = 0;
             mSurfaceWidth = width;
@@ -205,7 +213,7 @@
 
     @Override
     void setOpaque(boolean opaque) {
-        nSetOpaque(mNativeProxy, opaque);
+        nSetOpaque(mNativeProxy, opaque && !mHasInsets);
     }
 
     @Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a09a061..fdbc619 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4852,8 +4852,8 @@
      */
     private void manageFocusHotspot(boolean focused, View v) {
         final Rect r = new Rect();
-        if (!focused && v != null && mAttachInfo != null) {
-            v.getBoundsOnScreen(r);
+        if (v != null && mAttachInfo != null) {
+            v.getHotspotBounds(r);
             final int[] location = mAttachInfo.mTmpLocation;
             getLocationOnScreen(location);
             r.offset(-location[0], -location[1]);
@@ -4867,6 +4867,22 @@
     }
 
     /**
+     * Populates <code>outRect</code> with the hotspot bounds. By default,
+     * the hotspot bounds are identical to the screen bounds.
+     *
+     * @param outRect rect to populate with hotspot bounds
+     * @hide Only for internal use by views and widgets.
+     */
+    public void getHotspotBounds(Rect outRect) {
+        final Drawable background = getBackground();
+        if (background != null) {
+            background.getHotspotBounds(outRect);
+        } else {
+            getBoundsOnScreen(outRect);
+        }
+    }
+
+    /**
      * Request that a rectangle of this view be visible on the screen,
      * scrolling if necessary just enough.
      *
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
new file mode 100644
index 0000000..dc7c808
--- /dev/null
+++ b/core/java/android/webkit/WebResourceRequest.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 android.webkit;
+
+import android.net.Uri;
+
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * Encompasses parameters to the {@link WebViewClient#shouldInterceptRequest} method.
+ */
+public interface WebResourceRequest {
+    /**
+     * Gets the URL for which the resource request was made.
+     *
+     * @return the URL for which the resource request was made.
+     */
+    Uri getUrl();
+
+    /**
+     * Gets whether the request was made for the main frame.
+     *
+     * @return whether the request was made for the main frame. Will be false for iframes,
+     *         for example.
+     */
+    boolean isForMainFrame();
+
+    /**
+     * Gets whether a gesture was associated with the request.
+     * <p>
+     * <strong>IMPORTANT:</strong>
+     * This should not be used to implement any form of security. It is possible for the content
+     * to spoof this.
+     *
+     * @return whether a gesture was associated with the request.
+     */
+    boolean hasUserGestureInsecure();
+
+    /**
+     * Gets the method associated with the request, for example "GET".
+     *
+     * @return the method associated with the request.
+     */
+    String getMethod();
+
+    /**
+     * Gets the headers associated with the request. These are represented as a mapping of header
+     * name to header value.
+     *
+     * @return the headers associated with the request.
+     */
+    Map<String, String> getRequestHeaders();
+}
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index f21e2b4..ad6e9aa 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import java.io.InputStream;
+import java.util.Map;
 
 /**
  * Encapsulates a resource response. Applications can return an instance of this
@@ -24,9 +25,11 @@
  * response when the WebView requests a particular resource.
  */
 public class WebResourceResponse {
-    // Accessed by jni, do not rename without modifying the jni code.
     private String mMimeType;
     private String mEncoding;
+    private int mStatusCode;
+    private String mReasonPhrase;
+    private Map<String, String> mResponseHeaders;
     private InputStream mInputStream;
 
     /**
@@ -47,6 +50,28 @@
     }
 
     /**
+     * Constructs a resource response with the given parameters. Callers must
+     * implement {@link InputStream#read(byte[]) InputStream.read(byte[])} for
+     * the input stream.
+     *
+     * @param mimeType the resource response's MIME type, for example text/html
+     * @param encoding the resource response's encoding
+     * @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
+     *                   Causing a redirect by specifying a 3xx code is not supported.
+     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
+     *                     and not empty.
+     * @param responseHeaders the resource response's headers represented as a mapping of header
+     *                        name -> header value.
+     * @param data the input stream that provides the resource response's data
+     */
+    public WebResourceResponse(String mimeType, String encoding, int statusCode,
+            String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
+        this(mimeType, encoding, data);
+        setStatusCodeAndReasonPhrase(statusCode, reasonPhrase);
+        setResponseHeaders(responseHeaders);
+    }
+
+    /**
      * Sets the resource response's MIME type, for example text/html.
      *
      * @param mimeType the resource response's MIME type
@@ -84,7 +109,73 @@
     }
 
     /**
-     * Sets the input stream that provides the resource respone's data. Callers
+     * Sets the resource response's status code and reason phrase.
+     *
+     * @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
+     *                   Causing a redirect by specifying a 3xx code is not supported.
+     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
+     *                     and not empty.
+     */
+    public void setStatusCodeAndReasonPhrase(int statusCode, String reasonPhrase) {
+        if (statusCode < 100)
+            throw new IllegalArgumentException("statusCode can't be less than 100.");
+        if (statusCode > 599)
+            throw new IllegalArgumentException("statusCode can't be greater than 599.");
+        if (statusCode > 299 && statusCode < 400)
+            throw new IllegalArgumentException("statusCode can't be in the [300, 399] range.");
+        if (reasonPhrase == null)
+            throw new IllegalArgumentException("reasonPhrase can't be null.");
+        if (reasonPhrase.trim().isEmpty())
+            throw new IllegalArgumentException("reasonPhrase can't be empty.");
+        for (int i = 0; i < reasonPhrase.length(); i++) {
+            int c = reasonPhrase.charAt(i);
+            if (c > 0x7F) {
+                throw new IllegalArgumentException(
+                        "reasonPhrase can't contain non-ASCII characters.");
+            }
+        }
+        mStatusCode = statusCode;
+        mReasonPhrase = reasonPhrase;
+    }
+
+    /**
+     * Gets the resource response's status code.
+     *
+     * @return the resource response's status code.
+     */
+    public int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /**
+     * Gets the description of the resource response's status code.
+     *
+     * @return the description of the resource response's status code.
+     */
+    public String getReasonPhrase() {
+        return mReasonPhrase;
+    }
+
+    /**
+     * Sets the headers for the resource response.
+     *
+     * @param headers mapping of header name -> header value.
+     */
+    public void setResponseHeaders(Map<String, String> headers) {
+        mResponseHeaders = headers;
+    }
+
+    /**
+     * Gets the headers for the resource response.
+     *
+     * @return the headers for the resource response.
+     */
+    public Map<String, String> getResponseHeaders() {
+        return mResponseHeaders;
+    }
+
+    /**
+     * Sets the input stream that provides the resource response's data. Callers
      * must implement {@link InputStream#read(byte[]) InputStream.read(byte[])}.
      *
      * @param data the input stream that provides the resource response's data
@@ -94,7 +185,7 @@
     }
 
     /**
-     * Gets the input stream that provides the resource respone's data.
+     * Gets the input stream that provides the resource response's data.
      *
      * @return the input stream that provides the resource response's data
      */
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 290a574..adf4803 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2471,4 +2471,16 @@
         mProvider.getViewDelegate().preDispatchDraw(canvas);
         super.dispatchDraw(canvas);
     }
+
+    @Override
+    public void onStartTemporaryDetach() {
+        super.onStartTemporaryDetach();
+        mProvider.getViewDelegate().onStartTemporaryDetach();
+    }
+
+    @Override
+    public void onFinishTemporaryDetach() {
+        super.onFinishTemporaryDetach();
+        mProvider.getViewDelegate().onFinishTemporaryDetach();
+    }
 }
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 62b80c4a..d52dd60 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -96,13 +96,36 @@
      * @return A {@link android.webkit.WebResourceResponse} containing the
      *         response information or null if the WebView should load the
      *         resource itself.
+     * @deprecated Use {@link #shouldInterceptRequest(WebView, WebResourceRequest)
+     *             shouldInterceptRequest(WebView, WebResourceRequest)} instead.
      */
+    @Deprecated
     public WebResourceResponse shouldInterceptRequest(WebView view,
             String url) {
         return null;
     }
 
     /**
+     * Notify the host application of a resource request and allow the
+     * application to return the data.  If the return value is null, the WebView
+     * will continue to load the resource as usual.  Otherwise, the return
+     * response and data will be used.  NOTE: This method is called on a thread
+     * other than the UI thread so clients should exercise caution
+     * when accessing private data or the view system.
+     *
+     * @param view The {@link android.webkit.WebView} that is requesting the
+     *             resource.
+     * @param request Object containing the details of the request.
+     * @return A {@link android.webkit.WebResourceResponse} containing the
+     *         response information or null if the WebView should load the
+     *         resource itself.
+     */
+    public WebResourceResponse shouldInterceptRequest(WebView view,
+            WebResourceRequest request) {
+        return shouldInterceptRequest(view, request.getUrl().toString());
+    }
+
+    /**
      * Notify the host application that there have been an excessive number of
      * HTTP redirects. As the host application if it would like to continue
      * trying to load the resource. The default behavior is to send the cancel
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 5081ff5..b6fd363 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -360,6 +360,10 @@
         public void setLayerType(int layerType, Paint paint);
 
         public void preDispatchDraw(Canvas canvas);
+
+        public void onStartTemporaryDetach();
+
+        public void onFinishTemporaryDetach();
     }
 
     interface ScrollDelegate {
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 2c1a77c..8f49fb8 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -17,7 +17,9 @@
 package android.widget;
 
 import android.annotation.Widget;
+import android.app.UiModeManager;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.os.Parcel;
@@ -49,6 +51,8 @@
 
 import libcore.icu.ICU;
 
+import static android.os.Build.VERSION_CODES.L;
+
 /**
  * This class is a widget for selecting a date. The date can be selected by a
  * year, month, and day spinners or a {@link CalendarView}. The set of spinners
@@ -70,6 +74,15 @@
  * @attr ref android.R.styleable#DatePicker_minDate
  * @attr ref android.R.styleable#DatePicker_spinnersShown
  * @attr ref android.R.styleable#DatePicker_calendarViewShown
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekBackgroundColor
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorBackgroundColor
+ * @attr ref android.R.styleable#DatePicker_dateSelectorMonthTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfMonthTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearListItemTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearListSelectedCircleColor
+ * @attr ref android.R.styleable#DatePicker_calendarTextColor
  */
 @Widget
 public class DatePicker extends FrameLayout {
@@ -78,6 +91,10 @@
 
     private DatePickerDelegate mDelegate;
 
+    private int mDefStyleAttr;
+    private int mDefStyleRes;
+    private Context mContext;
+
     /**
      * The callback used to indicate the user changes\d the date.
      */
@@ -110,7 +127,65 @@
     public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
-        mDelegate = new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+        mContext = context;
+        mDefStyleAttr = defStyleAttr;
+        mDefStyleRes = defStyleRes;
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
+                mDefStyleAttr, mDefStyleRes);
+
+        // Create the correct UI delegate. Default is the legacy one.
+        final boolean isLegacyMode = a.getBoolean(R.styleable.DatePicker_legacyMode,
+                isLegacyMode());
+
+        a.recycle();
+
+        setLegacyMode(isLegacyMode, attrs);
+    }
+
+    private boolean isLegacyMode() {
+        UiModeManager uiModeManager =
+                (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+            return true;
+        }
+        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+        return targetSdkVersion < L;
+    }
+
+    private DatePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        return new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private DatePickerDelegate createNewUIDelegate(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        return new android.widget.DatePickerDelegate(this, context, attrs, defStyleAttr,
+                defStyleRes);
+    }
+
+    /**
+     * @hide
+     */
+    public void setLegacyMode(boolean isLegacyMode, AttributeSet attrs) {
+        removeAllViewsInLayout();
+        mDelegate = isLegacyMode ?
+                createLegacyUIDelegate(mContext, attrs, mDefStyleAttr, mDefStyleRes) :
+                createNewUIDelegate(mContext, attrs, mDefStyleAttr, mDefStyleRes);
+    }
+
+    /**
+     * @hide
+     */
+    public void setShowDoneButton(boolean showDoneButton) {
+        mDelegate.setShowDoneButton(showDoneButton);
+    }
+
+    /**
+     * @hide
+     */
+    public void setDismissCallback(DatePickerDismissCallback callback) {
+        mDelegate.setDismissCallback(callback);
     }
 
     /**
@@ -129,7 +204,7 @@
     }
 
     /**
-     * Updates the current date.
+     * Update the current date.
      *
      * @param year The year.
      * @param month The month which is <strong>starting from zero</strong>.
@@ -171,7 +246,7 @@
      * @return The minimal supported date.
      */
     public long getMinDate() {
-        return mDelegate.getMinDate();
+        return mDelegate.getMinDate().getTimeInMillis();
     }
 
     /**
@@ -196,7 +271,7 @@
      * @return The maximal supported date.
      */
     public long getMaxDate() {
-        return mDelegate.getMaxDate();
+        return mDelegate.getMaxDate().getTimeInMillis();
     }
 
     /**
@@ -300,6 +375,182 @@
         mDelegate.setSpinnersShown(shown);
     }
 
+    /**
+     * Sets the background color for the date selector's day of week.
+     *
+     * @param color The background color.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekBackgroundColor
+     */
+    public void setDateSelectorDayOfWeekBackgroundColor(int color) {
+        mDelegate.setDateSelectorDayOfWeekBackgroundColor(color);
+    }
+
+    /**
+     * Gets the background color for the date selector's day of week.
+     *
+     * @return The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekBackgroundColor
+     */
+    public int getDateSelectorDayOfWeekBackgroundColor() {
+        return mDelegate.getDateSelectorDayOfWeekBackgroundColor();
+    }
+
+    /**
+     * Sets the text appearance for the date selector's day of week.
+     *
+     * @param resId The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekTextAppearance
+     */
+    public void setDateSelectorDayOfWeekTextAppearance(int resId) {
+        mDelegate.setDateSelectorDayOfWeekTextAppearance(resId);
+    }
+
+    /**
+     * Gets the text appearance for the date selector's day of week.
+     *
+     * @return The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekTextAppearance
+     */
+    public int getDateSelectorDayOfWeekTextAppearance() {
+        return mDelegate.getDateSelectorDayOfWeekTextAppearance();
+    }
+
+    /**
+     * Sets the background color for the date selector's.
+     *
+     * @param color The background color.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorBackgroundColor
+     */
+    public void setDateSelectorBackgroundColor(int color) {
+        mDelegate.setDateSelectorBackgroundColor(color);
+    }
+
+    /**
+     * Gets the background color for the date selector's.
+     *
+     * @return The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorBackgroundColor
+     */
+    public int getDateSelectorBackgroundColor() {
+        return mDelegate.getDateSelectorBackgroundColor();
+    }
+
+    /**
+     * Sets the text appearance for the date selector's month.
+     *
+     * @param resId The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorMonthTextAppearance
+     */
+    public void setDateSelectorMonthTextAppearance(int resId) {
+        mDelegate.setDateSelectorMonthTextAppearance(resId);
+    }
+
+    /**
+     * Gets the text appearance for the date selector's month.
+     *
+     * @return The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorMonthTextAppearance
+     */
+    public int getDateSelectorMonthTextAppearance() {
+        return mDelegate.getDateSelectorMonthTextAppearance();
+    }
+
+    /**
+     * Sets the text appearance for the date selector's day of month.
+     *
+     * @param resId The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfMonthTextAppearance
+     */
+    public void setDateSelectorDayOfMonthTextAppearance(int resId) {
+        mDelegate.setDateSelectorDayOfMonthTextAppearance(resId);
+    }
+
+    /**
+     * Gets the text appearance for the date selector's day of month.
+     *
+     * @return The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfMonthTextAppearance
+     */
+    public int getDateSelectorDayOfMonthTextAppearance() {
+        return mDelegate.getDateSelectorDayOfMonthTextAppearance();
+    }
+
+    /**
+     * Sets the text appearance for the date selector's year.
+     *
+     * @param resId The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorYearTextAppearance
+     */
+    public void setDateSelectorYearTextAppearance(int resId) {
+        mDelegate.setDateSelectorYearTextAppearance(resId);
+    }
+
+    /**
+     * Gets the text appearance for the date selector's year.
+     *
+     * @return The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorYearTextAppearance
+     */
+    public int getDateSelectorYearTextAppearance() {
+        return mDelegate.getDateSelectorYearTextAppearance();
+    }
+
+    /**
+     * Sets the text appearance for the year list item.
+     *
+     * @param resId The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorYearListItemTextAppearance
+     */
+    public void setDateSelectorYearListItemTextAppearance(int resId) {
+        mDelegate.setDateSelectorYearListItemTextAppearance(resId);
+    }
+
+    /**
+     * Gets the text appearance for the year list item.
+     *
+     * @return The text appearance resource id.
+     *
+     * @attr ref android.R.styleable#DatePicker_dateSelectorYearListItemTextAppearance
+     */
+    public int getDateSelectorYearListItemTextAppearance() {
+        return mDelegate.getDateSelectorYearListItemTextAppearance();
+    }
+
+    /**
+     * Sets the text color state list for the calendar.
+     *
+     * @param colors The text color state list.
+     *
+     * @attr ref android.R.styleable#DatePicker_calendarTextColor
+     */
+    public void setCalendarTextColor(ColorStateList colors) {
+        mDelegate.setCalendarTextColor(colors);
+    }
+
+    /**
+     * Gets the text color state list for the calendar.
+     *
+     * @return The text color state list for the calendar.
+     *
+     * @attr ref android.R.styleable#DatePicker_calendarTextColor
+     */
+    public ColorStateList getCalendarTextColor() {
+        return mDelegate.getCalendarTextColors();
+    }
+
     // Override so we are in complete control of save / restore for this widget.
     @Override
     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
@@ -323,6 +574,8 @@
      * A delegate interface that defined the public API of the DatePicker. Allows different
      * DatePicker implementations. This would need to be implemented by the DatePicker delegates
      * for the real behavior.
+     *
+     * @hide
      */
     interface DatePickerDelegate {
         void init(int year, int monthOfYear, int dayOfMonth,
@@ -335,15 +588,42 @@
         int getDayOfMonth();
 
         void setMinDate(long minDate);
-        long getMinDate();
+        Calendar getMinDate();
 
         void setMaxDate(long maxDate);
-        long getMaxDate();
+        Calendar getMaxDate();
 
         void setEnabled(boolean enabled);
         boolean isEnabled();
 
-        CalendarView getCalendarView ();
+        void setDateSelectorDayOfWeekBackgroundColor(int color);
+        int getDateSelectorDayOfWeekBackgroundColor();
+
+        void setDateSelectorDayOfWeekTextAppearance(int resId);
+        int getDateSelectorDayOfWeekTextAppearance();
+
+        void setDateSelectorBackgroundColor(int color);
+        int getDateSelectorBackgroundColor();
+
+        void setDateSelectorMonthTextAppearance(int resId);
+        int getDateSelectorMonthTextAppearance();
+
+        void setDateSelectorDayOfMonthTextAppearance(int resId);
+        int getDateSelectorDayOfMonthTextAppearance();
+
+        void setDateSelectorYearTextAppearance(int resId);
+        int getDateSelectorYearTextAppearance();
+
+        void setDateSelectorYearListItemTextAppearance(int resId);
+        int getDateSelectorYearListItemTextAppearance();
+
+        void setDateSelectorYearListSelectedCircleColor(int color);
+        int getDateSelectorYearListSelectedCircleColor();
+
+        void setCalendarTextColor(ColorStateList colors);
+        ColorStateList getCalendarTextColors();
+
+        CalendarView getCalendarView();
 
         void setCalendarViewShown(boolean shown);
         boolean getCalendarViewShown();
@@ -351,6 +631,9 @@
         void setSpinnersShown(boolean shown);
         boolean getSpinnersShown();
 
+        void setShowDoneButton(boolean showDoneButton);
+        void setDismissCallback(DatePickerDismissCallback callback);
+
         void onConfigurationChanged(Configuration newConfig);
 
         void dispatchRestoreInstanceState(SparseArray<Parcelable> container);
@@ -366,7 +649,7 @@
     /**
      * An abstract class which can be used as a start for DatePicker implementations
      */
-    abstract static class AbstractTimePickerDelegate implements DatePickerDelegate {
+    abstract static class AbstractDatePickerDelegate implements DatePickerDelegate {
         // The delegator
         protected DatePicker mDelegator;
 
@@ -379,7 +662,7 @@
         // Callbacks
         protected  OnDateChangedListener mOnDateChangedListener;
 
-        public AbstractTimePickerDelegate(DatePicker delegator, Context context) {
+        public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
             mDelegator = delegator;
             mContext = context;
 
@@ -396,9 +679,18 @@
     }
 
     /**
+     * A callback interface for dismissing the DatePicker when included into a Dialog
+     *
+     * @hide
+     */
+    public static interface DatePickerDismissCallback {
+        void dismiss(DatePicker view, boolean isCancel, int year, int month, int dayOfMonth);
+    }
+
+    /**
      * A delegate implementing the basic DatePicker
      */
-    private static class LegacyDatePickerDelegate extends AbstractTimePickerDelegate {
+    private static class LegacyDatePickerDelegate extends AbstractDatePickerDelegate {
 
         private static final String DATE_FORMAT = "MM/dd/yyyy";
 
@@ -466,7 +758,7 @@
             String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
             String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
             int layoutResourceId = attributesArray.getResourceId(
-                    R.styleable.DatePicker_internalLayout, R.layout.date_picker);
+                    R.styleable.DatePicker_legacyLayout, R.layout.date_picker_legacy);
             attributesArray.recycle();
 
             LayoutInflater inflater = (LayoutInflater) context
@@ -643,8 +935,10 @@
         }
 
         @Override
-        public long getMinDate() {
-            return mCalendarView.getMinDate();
+        public Calendar getMinDate() {
+            final Calendar minDate = Calendar.getInstance();
+            minDate.setTimeInMillis(mCalendarView.getMinDate());
+            return minDate;
         }
 
         @Override
@@ -664,8 +958,10 @@
         }
 
         @Override
-        public long getMaxDate() {
-            return mCalendarView.getMaxDate();
+        public Calendar getMaxDate() {
+            final Calendar maxDate = Calendar.getInstance();
+            maxDate.setTimeInMillis(mCalendarView.getMaxDate());
+            return maxDate;
         }
 
         @Override
@@ -683,6 +979,87 @@
         }
 
         @Override
+        public void setDateSelectorDayOfWeekBackgroundColor(int color) {
+        }
+
+        @Override
+        public int getDateSelectorDayOfWeekBackgroundColor() {
+            return 0;
+        }
+
+        @Override
+        public void setDateSelectorDayOfWeekTextAppearance(int resId) {
+        }
+
+        @Override
+        public int getDateSelectorDayOfWeekTextAppearance() {
+            return 0;
+        }
+
+        @Override
+        public void setDateSelectorBackgroundColor(int color) {
+        }
+
+        @Override
+        public int getDateSelectorBackgroundColor() {
+            return 0;
+        }
+
+        @Override
+        public void setDateSelectorMonthTextAppearance(int resId) {
+        }
+
+        @Override
+        public int getDateSelectorMonthTextAppearance() {
+            return 0;
+        }
+
+        @Override
+        public void setDateSelectorDayOfMonthTextAppearance(int resId) {
+        }
+
+        @Override
+        public int getDateSelectorDayOfMonthTextAppearance() {
+            return 0;
+        }
+
+        @Override
+        public void setDateSelectorYearTextAppearance(int resId) {
+        }
+
+        @Override
+        public int getDateSelectorYearTextAppearance() {
+            return 0;
+        }
+
+        @Override
+        public void setDateSelectorYearListItemTextAppearance(int resId) {
+        }
+
+        @Override
+        public int getDateSelectorYearListItemTextAppearance() {
+            return 0;
+        }
+
+        @Override
+        public void setDateSelectorYearListSelectedCircleColor(int color) {
+        }
+
+        @Override
+        public int getDateSelectorYearListSelectedCircleColor() {
+            return 0;
+        }
+
+        @Override
+        public void setCalendarTextColor(ColorStateList colors) {
+        }
+
+        @Override
+        public ColorStateList getCalendarTextColors() {
+            return ColorStateList.valueOf(0);
+        }
+
+        @Override
         public CalendarView getCalendarView() {
             return mCalendarView;
         }
@@ -708,6 +1085,16 @@
         }
 
         @Override
+        public void setShowDoneButton(boolean showDoneButton) {
+            // Nothing to do
+        }
+
+        @Override
+        public void setDismissCallback(DatePickerDismissCallback callback) {
+            // Nothing to do
+        }
+
+        @Override
         public void onConfigurationChanged(Configuration newConfig) {
             setCurrentLocale(newConfig.locale);
         }
diff --git a/core/java/android/widget/DatePickerController.java b/core/java/android/widget/DatePickerController.java
new file mode 100644
index 0000000..6a074da
--- /dev/null
+++ b/core/java/android/widget/DatePickerController.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.widget;
+
+import java.util.Calendar;
+
+/**
+ * Controller class to communicate among the various components of the date picker dialog.
+ *
+ * @hide
+ */
+interface DatePickerController {
+
+    void onYearSelected(int year);
+
+    void onDayOfMonthSelected(int year, int month, int day);
+
+    void registerOnDateChangedListener(OnDateChangedListener listener);
+
+    void unregisterOnDateChangedListener(OnDateChangedListener listener);
+
+    Calendar getSelectedDay();
+
+    int getFirstDayOfWeek();
+
+    int getMinYear();
+    int getMaxYear();
+
+    int getMinMonth();
+    int getMaxMonth();
+
+    int getMinDay();
+    int getMaxDay();
+
+    void setMinDate(long minDate);
+    Calendar getMinDate();
+
+    void setMaxDate(long maxDate);
+    Calendar getMaxDate();
+
+    void tryVibrate();
+}
diff --git a/core/java/android/widget/DatePickerDelegate.java b/core/java/android/widget/DatePickerDelegate.java
new file mode 100644
index 0000000..31044d4
--- /dev/null
+++ b/core/java/android/widget/DatePickerDelegate.java
@@ -0,0 +1,979 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.HapticFeedbackConstants;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+
+import com.android.internal.R;
+import com.android.internal.widget.AccessibleDateAnimator;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ * A delegate for picking up a date (day / month / year).
+ */
+class DatePickerDelegate extends DatePicker.AbstractDatePickerDelegate implements
+        View.OnClickListener, DatePickerController {
+
+    private static final int UNINITIALIZED = -1;
+    private static final int MONTH_AND_DAY_VIEW = 0;
+    private static final int YEAR_VIEW = 1;
+
+    private static final int DEFAULT_START_YEAR = 1900;
+    private static final int DEFAULT_END_YEAR = 2100;
+
+    private static final int PULSE_ANIMATOR_DURATION = 544;
+
+    private static final int ANIMATION_DURATION = 300;
+    private static final int ANIMATION_DELAY = 650;
+
+    private static final int MONTH_INDEX = 0;
+    private static final int DAY_INDEX = 1;
+    private static final int YEAR_INDEX = 2;
+
+    private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault());
+    private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault());
+
+    private TextView mDayOfWeekView;
+    private LinearLayout mDateLayout;
+    private LinearLayout mMonthAndDayLayout;
+    private TextView mSelectedMonthTextView;
+    private TextView mSelectedDayTextView;
+    private TextView mSelectedYearView;
+    private DayPickerView mDayPickerView;
+    private YearPickerView mYearPickerView;
+
+    private ViewGroup mLayoutButtons;
+
+    private boolean mIsEnabled = true;
+
+    // Accessibility strings.
+    private String mDayPickerDescription;
+    private String mSelectDay;
+    private String mYearPickerDescription;
+    private String mSelectYear;
+
+    private AccessibleDateAnimator mAnimator;
+
+    private DatePicker.OnDateChangedListener mDateChangedListener;
+
+    private boolean mDelayAnimation = true;
+
+    private int mCurrentView = UNINITIALIZED;
+
+    private Calendar mCurrentDate;
+    private Calendar mTempDate;
+    private Calendar mMinDate;
+    private Calendar mMaxDate;
+
+    // For showing the done button when in a Dialog
+    private Button mDoneButton;
+    private boolean mShowDoneButton;
+    private DatePicker.DatePickerDismissCallback mDismissCallback;
+
+    private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();
+
+    private int mDayOfWeekTextAppearanceResId;
+    private int mMonthTextAppearanceResId;
+    private int mDayOfMonthTextAppearanceResId;
+    private int mYearTextAppearanceResId;
+
+    private int mYearListItemTextAppearanceResId;
+
+    private int mDayOfWeekBackgroundColor;
+    private int mMonthAndDayBackgroundColor;
+
+    private ColorStateList mCalendarTextColors;
+
+    public DatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+
+        super(delegator, context);
+
+        final Locale locale = Locale.getDefault();
+        mMinDate = getCalendarForLocale(mMinDate, locale);
+        mMaxDate = getCalendarForLocale(mMaxDate, locale);
+        mTempDate = getCalendarForLocale(mMaxDate, locale);
+
+        mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
+
+        mMinDate.set(DEFAULT_START_YEAR, 1, 1);
+        mMaxDate.set(DEFAULT_END_YEAR, 12, 31);
+
+        // process style attributes
+        final TypedArray a = mContext.obtainStyledAttributes(attrs,
+                R.styleable.DatePicker, defStyleAttr, defStyleRes);
+
+        final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        final int layoutResourceId = a.getResourceId(
+                R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo);
+
+        View mainView = inflater.inflate(layoutResourceId, null);
+        mDelegator.addView(mainView);
+
+        mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header);
+        mDateLayout = (LinearLayout) mainView.findViewById(R.id.day_picker_selector_layout);
+        mMonthAndDayLayout = (LinearLayout) mainView.findViewById(
+                R.id.date_picker_month_and_day_layout);
+        mMonthAndDayLayout.setOnClickListener(this);
+        mSelectedMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month);
+        mSelectedDayTextView = (TextView) mainView.findViewById(R.id.date_picker_day);
+        mSelectedYearView = (TextView) mainView.findViewById(R.id.date_picker_year);
+        mSelectedYearView.setOnClickListener(this);
+
+        // Use Theme attributes if possible
+        mDayOfWeekTextAppearanceResId = a.getResourceId(
+                R.styleable.DatePicker_dateSelectorDayOfWeekTextAppearance, -1);
+        if (mDayOfWeekTextAppearanceResId != -1) {
+            mDayOfWeekView.setTextAppearance(context, mDayOfWeekTextAppearanceResId);
+        }
+
+        mMonthTextAppearanceResId = a.getResourceId(
+                R.styleable.DatePicker_dateSelectorMonthTextAppearance, -1);
+        if (mMonthTextAppearanceResId != -1) {
+            mSelectedMonthTextView.setTextAppearance(context, mMonthTextAppearanceResId);
+        }
+
+        mDayOfMonthTextAppearanceResId = a.getResourceId(
+                R.styleable.DatePicker_dateSelectorDayOfMonthTextAppearance, -1);
+        if (mDayOfMonthTextAppearanceResId != -1) {
+            mSelectedDayTextView.setTextAppearance(context, mDayOfMonthTextAppearanceResId);
+        }
+
+        mYearTextAppearanceResId = a.getResourceId(
+                R.styleable.DatePicker_dateSelectorYearTextAppearance, -1);
+        if (mYearTextAppearanceResId != -1) {
+            mSelectedYearView.setTextAppearance(context, mYearTextAppearanceResId);
+        }
+
+        Resources res = mDelegator.getResources();
+
+        mDayOfWeekBackgroundColor = a.getColor(
+                R.styleable.DatePicker_dateSelectorDayOfWeekBackgroundColor,
+                res.getColor(
+                        R.color.datepicker_default_header_dayofweek_background_color_holo_light));
+        mDayOfWeekView.setBackgroundColor(mDayOfWeekBackgroundColor);
+
+        mMonthAndDayBackgroundColor = a.getColor(R.styleable.DatePicker_dateSelectorBackgroundColor,
+                res.getColor(R.color.datepicker_default_header_selector_background_holo_light));
+        mMonthAndDayLayout.setBackgroundColor(mMonthAndDayBackgroundColor);
+
+        mDayPickerView = new DayPickerView(mContext, this);
+        mYearPickerView = new YearPickerView(mContext);
+        mYearPickerView.init(this);
+
+        ColorStateList colors = a.getColorStateList(R.styleable.DatePicker_calendarTextColor);
+        setCalendarTextColor(colors);
+
+        mDayPickerDescription = res.getString(R.string.day_picker_description);
+        mSelectDay = res.getString(R.string.select_day);
+        mYearPickerDescription = res.getString(R.string.year_picker_description);
+        mSelectYear = res.getString(R.string.select_year);
+
+        mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator);
+        mAnimator.addView(mDayPickerView);
+        mAnimator.addView(mYearPickerView);
+        mAnimator.setDateMillis(mCurrentDate.getTimeInMillis());
+        Animation animation = new AlphaAnimation(0.0f, 1.0f);
+        animation.setDuration(ANIMATION_DURATION);
+        mAnimator.setInAnimation(animation);
+        Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
+        animation2.setDuration(ANIMATION_DURATION);
+        mAnimator.setOutAnimation(animation2);
+
+        mLayoutButtons = (ViewGroup) mainView.findViewById(R.id.layout_buttons);
+        mDoneButton = (Button) mainView.findViewById(R.id.done);
+        mDoneButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                tryVibrate();
+                if (mDismissCallback != null) {
+                    mDismissCallback.dismiss(mDelegator, false, mCurrentDate.get(Calendar.YEAR),
+                            mCurrentDate.get(Calendar.MONTH),
+                            mCurrentDate.get(Calendar.DAY_OF_MONTH));
+                }
+            }
+        });
+
+        updateDisplay(false);
+        setCurrentView(MONTH_AND_DAY_VIEW);
+    }
+
+    /**
+     * Gets a calendar for locale bootstrapped with the value of a given calendar.
+     *
+     * @param oldCalendar The old calendar.
+     * @param locale The locale.
+     */
+    private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
+        if (oldCalendar == null) {
+            return Calendar.getInstance(locale);
+        } else {
+            final long currentTimeMillis = oldCalendar.getTimeInMillis();
+            Calendar newCalendar = Calendar.getInstance(locale);
+            newCalendar.setTimeInMillis(currentTimeMillis);
+            return newCalendar;
+        }
+    }
+
+    /**
+     * Compute the array representing the order of Month / Day / Year views in their layout.
+     * Will be used for I18N purpose as the order of them depends on the Locale.
+     */
+    private int[] getMonthDayYearIndexes(String pattern) {
+        int[] result = new int[3];
+
+        final String filteredPattern = pattern.replaceAll("'.*?'", "");
+
+        final int dayIndex = filteredPattern.indexOf('d');
+        final int monthMIndex = filteredPattern.indexOf("M");
+        final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L");
+        final int yearIndex = filteredPattern.indexOf("y");
+
+        if (yearIndex < monthIndex) {
+            result[YEAR_INDEX] = 0;
+
+            if (monthIndex < dayIndex) {
+                result[MONTH_INDEX] = 1;
+                result[DAY_INDEX] = 2;
+            } else {
+                result[MONTH_INDEX] = 2;
+                result[DAY_INDEX] = 1;
+            }
+        } else {
+            result[YEAR_INDEX] = 2;
+
+            if (monthIndex < dayIndex) {
+                result[MONTH_INDEX] = 0;
+                result[DAY_INDEX] = 1;
+            } else {
+                result[MONTH_INDEX] = 1;
+                result[DAY_INDEX] = 0;
+            }
+        }
+        return result;
+    }
+
+    private void updateDisplay(boolean announce) {
+        if (mDayOfWeekView != null) {
+            mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
+                    Locale.getDefault()));
+        }
+        final String bestDateTimePattern =
+                DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd");
+
+        // Compute indices of Month, Day and Year views
+        int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern);
+
+        // Restart from a clean state
+        mMonthAndDayLayout.removeAllViews();
+        mDateLayout.removeView(mSelectedYearView);
+
+        // Position the Year View at the correct location
+        if (viewIndices[YEAR_INDEX] == 0) {
+            mDateLayout.addView(mSelectedYearView, 0);
+        } else {
+            mDateLayout.addView(mSelectedYearView, 1);
+        }
+
+        // Position Day and Month Views
+        if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) {
+            // Day View is first
+            mMonthAndDayLayout.addView(mSelectedDayTextView);
+            mMonthAndDayLayout.addView(mSelectedMonthTextView);
+        } else {
+            // Month View is first
+            mMonthAndDayLayout.addView(mSelectedMonthTextView);
+            mMonthAndDayLayout.addView(mSelectedDayTextView);
+        }
+
+        mSelectedMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT,
+                Locale.getDefault()).toUpperCase(Locale.getDefault()));
+        mSelectedDayTextView.setText(mDayFormat.format(mCurrentDate.getTime()));
+        mSelectedYearView.setText(mYearFormat.format(mCurrentDate.getTime()));
+
+        // Accessibility.
+        long millis = mCurrentDate.getTimeInMillis();
+        mAnimator.setDateMillis(millis);
+        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
+        String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags);
+        mMonthAndDayLayout.setContentDescription(monthAndDayText);
+
+        if (announce) {
+            flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
+            String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
+            mAnimator.announceForAccessibility(fullDateText);
+        }
+        updatePickers();
+    }
+
+    private void setCurrentView(final int viewIndex) {
+        long millis = mCurrentDate.getTimeInMillis();
+
+        switch (viewIndex) {
+            case MONTH_AND_DAY_VIEW:
+                ObjectAnimator pulseAnimator = getPulseAnimator(mMonthAndDayLayout, 0.9f,
+                        1.05f);
+                if (mDelayAnimation) {
+                    pulseAnimator.setStartDelay(ANIMATION_DELAY);
+                    mDelayAnimation = false;
+                }
+                mDayPickerView.onDateChanged();
+                if (mCurrentView != viewIndex) {
+                    mMonthAndDayLayout.setSelected(true);
+                    mSelectedYearView.setSelected(false);
+                    mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
+                    mCurrentView = viewIndex;
+                }
+                pulseAnimator.start();
+
+                int flags = DateUtils.FORMAT_SHOW_DATE;
+                String dayString = DateUtils.formatDateTime(mContext, millis, flags);
+                mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString);
+                mAnimator.announceForAccessibility(mSelectDay);
+                break;
+            case YEAR_VIEW:
+                pulseAnimator = getPulseAnimator(mSelectedYearView, 0.85f, 1.1f);
+                if (mDelayAnimation) {
+                    pulseAnimator.setStartDelay(ANIMATION_DELAY);
+                    mDelayAnimation = false;
+                }
+                mYearPickerView.onDateChanged();
+                if (mCurrentView != viewIndex) {
+                    mMonthAndDayLayout.setSelected(false);
+                    mSelectedYearView.setSelected(true);
+                    mAnimator.setDisplayedChild(YEAR_VIEW);
+                    mCurrentView = viewIndex;
+                }
+                pulseAnimator.start();
+
+                CharSequence yearString = mYearFormat.format(millis);
+                mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString);
+                mAnimator.announceForAccessibility(mSelectYear);
+                break;
+        }
+    }
+
+    @Override
+    public void init(int year, int monthOfYear, int dayOfMonth,
+            DatePicker.OnDateChangedListener callBack) {
+        mDateChangedListener = callBack;
+        mCurrentDate.set(Calendar.YEAR, year);
+        mCurrentDate.set(Calendar.MONTH, monthOfYear);
+        mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+        updateDisplay(false);
+    }
+
+    @Override
+    public void updateDate(int year, int month, int dayOfMonth) {
+        mCurrentDate.set(Calendar.YEAR, year);
+        mCurrentDate.set(Calendar.MONTH, month);
+        mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+        mDateChangedListener.onDateChanged(mDelegator, year, month, dayOfMonth);
+        updateDisplay(false);
+    }
+
+    @Override
+    public int getYear() {
+        return mCurrentDate.get(Calendar.YEAR);
+    }
+
+    @Override
+    public int getMonth() {
+        return mCurrentDate.get(Calendar.MONTH);
+    }
+
+    @Override
+    public int getDayOfMonth() {
+        return mCurrentDate.get(Calendar.DAY_OF_MONTH);
+    }
+
+    @Override
+    public void setMinDate(long minDate) {
+        mTempDate.setTimeInMillis(minDate);
+        if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
+                && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
+            return;
+        }
+        if (mCurrentDate.before(mTempDate)) {
+            mCurrentDate.setTimeInMillis(minDate);
+            updatePickers();
+            updateDisplay(false);
+        }
+        mMinDate.setTimeInMillis(minDate);
+        mDayPickerView.goTo(getSelectedDay(), false, true, true);
+    }
+
+    @Override
+    public Calendar getMinDate() {
+        return mMinDate;
+    }
+
+    @Override
+    public void setMaxDate(long maxDate) {
+        mTempDate.setTimeInMillis(maxDate);
+        if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
+                && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
+            return;
+        }
+        if (mCurrentDate.after(mTempDate)) {
+            mCurrentDate.setTimeInMillis(maxDate);
+            updatePickers();
+            updateDisplay(false);
+        }
+        mMaxDate.setTimeInMillis(maxDate);
+        mDayPickerView.goTo(getSelectedDay(), false, true, true);
+    }
+
+    @Override
+    public Calendar getMaxDate() {
+        return mMaxDate;
+    }
+
+    @Override
+    public int getFirstDayOfWeek() {
+        return mCurrentDate.getFirstDayOfWeek();
+    }
+
+    @Override
+    public int getMinYear() {
+        return mMinDate.get(Calendar.YEAR);
+    }
+
+    @Override
+    public int getMaxYear() {
+        return mMaxDate.get(Calendar.YEAR);
+    }
+
+    @Override
+    public int getMinMonth() {
+        return mMinDate.get(Calendar.MONTH);
+    }
+
+    @Override
+    public int getMaxMonth() {
+        return mMaxDate.get(Calendar.MONTH);
+    }
+
+    @Override
+    public int getMinDay() {
+        return mMinDate.get(Calendar.DAY_OF_MONTH);
+    }
+
+    @Override
+    public int getMaxDay() {
+        return mMaxDate.get(Calendar.DAY_OF_MONTH);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mMonthAndDayLayout.setEnabled(enabled);
+        mSelectedYearView.setEnabled(enabled);
+        mAnimator.setEnabled(enabled);
+        mIsEnabled = enabled;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    @Override
+    public void setDateSelectorDayOfWeekBackgroundColor(int color) {
+        if (mDayOfWeekBackgroundColor != color) {
+            mDayOfWeekBackgroundColor = color;
+            mDayOfWeekView.setBackgroundColor(color);
+        }
+    }
+
+    @Override
+    public int getDateSelectorDayOfWeekBackgroundColor() {
+        return mDayOfWeekBackgroundColor;
+    }
+
+    @Override
+    public void setDateSelectorDayOfWeekTextAppearance(int resId) {
+        if (mDayOfWeekTextAppearanceResId != resId && resId > 0) {
+            mDayOfWeekTextAppearanceResId = resId;
+            mDayOfWeekView.setTextAppearance(mContext, resId);
+        }
+    }
+
+    @Override
+    public int getDateSelectorDayOfWeekTextAppearance() {
+        return mDayOfWeekTextAppearanceResId;
+    }
+
+    @Override
+    public void setDateSelectorBackgroundColor(int color) {
+        if (mMonthAndDayBackgroundColor != color) {
+            mMonthAndDayBackgroundColor = color;
+            mMonthAndDayLayout.setBackgroundColor(color);
+        }
+    }
+
+    @Override
+    public int getDateSelectorBackgroundColor() {
+        return mMonthAndDayBackgroundColor;
+    }
+
+    @Override
+    public void setDateSelectorMonthTextAppearance(int resId) {
+        if (mMonthTextAppearanceResId != resId && resId > 0) {
+            mMonthTextAppearanceResId = resId;
+            mSelectedMonthTextView.setTextAppearance(mContext, resId);
+        }
+    }
+
+    @Override
+    public int getDateSelectorMonthTextAppearance() {
+        return mMonthTextAppearanceResId;
+    }
+
+    @Override
+    public void setDateSelectorDayOfMonthTextAppearance(int resId) {
+        if (mDayOfMonthTextAppearanceResId != resId && resId > 0) {
+            mDayOfMonthTextAppearanceResId = resId;
+            mSelectedDayTextView.setTextAppearance(mContext, resId);
+        }
+    }
+
+    @Override
+    public int getDateSelectorDayOfMonthTextAppearance() {
+        return mDayOfMonthTextAppearanceResId;
+    }
+
+    @Override
+    public void setDateSelectorYearTextAppearance(int resId) {
+        if (mYearTextAppearanceResId != resId && resId > 0) {
+            mYearTextAppearanceResId = resId;
+            mSelectedYearView.setTextAppearance(mContext, resId);
+        }
+    }
+
+    @Override
+    public int getDateSelectorYearTextAppearance() {
+        return mYearTextAppearanceResId;
+    }
+
+    @Override
+    public void setDateSelectorYearListItemTextAppearance(int resId) {
+        if (mYearListItemTextAppearanceResId != resId) {
+            mYearListItemTextAppearanceResId = resId;
+            mYearPickerView.setItemTextAppearance(resId);
+        }
+    }
+
+    @Override
+    public int getDateSelectorYearListItemTextAppearance() {
+        return mYearListItemTextAppearanceResId;
+    }
+
+    @Override
+    public void setDateSelectorYearListSelectedCircleColor(int color) {
+        mYearPickerView.setYearSelectedCircleColor(color);
+    }
+
+    @Override
+    public int getDateSelectorYearListSelectedCircleColor() {
+        return mYearPickerView.getYearSelectedCircleColor();
+    }
+
+    @Override
+    public void setCalendarTextColor(ColorStateList colors) {
+        if (colors == null) {
+            return;
+        }
+        if (mCalendarTextColors == null || !mCalendarTextColors.equals(colors)) {
+            mCalendarTextColors = colors;
+            mDayPickerView.setCalendarTextColor(colors);
+        }
+    }
+
+    @Override
+    public ColorStateList getCalendarTextColors() {
+        return mCalendarTextColors;
+    }
+
+    @Override
+    public CalendarView getCalendarView() {
+        throw new UnsupportedOperationException(
+                "CalendarView does not exists for the new DatePicker");
+    }
+
+    @Override
+    public void setCalendarViewShown(boolean shown) {
+        // No-op for compatibility with the old DatePicker.
+    }
+
+    @Override
+    public boolean getCalendarViewShown() {
+        return false;
+    }
+
+    @Override
+    public void setSpinnersShown(boolean shown) {
+        // No-op for compatibility with the old DatePicker.
+    }
+
+    @Override
+    public boolean getSpinnersShown() {
+        return false;
+    }
+
+    @Override
+    public void setShowDoneButton(boolean showDoneButton) {
+        mShowDoneButton = showDoneButton;
+        updateDoneButtonVisibility();
+    }
+
+    private void updateDoneButtonVisibility() {
+        mLayoutButtons.setVisibility(mShowDoneButton ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void setDismissCallback(DatePicker.DatePickerDismissCallback callback) {
+        mDismissCallback = callback;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        mYearFormat = new SimpleDateFormat("y", newConfig.locale);
+        mDayFormat = new SimpleDateFormat("d", newConfig.locale);
+    }
+
+    @Override
+    public void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        // Nothing to do
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState(Parcelable superState) {
+        final int year = mCurrentDate.get(Calendar.YEAR);
+        final int month = mCurrentDate.get(Calendar.MONTH);
+        final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
+
+        int listPosition = -1;
+        int listPositionOffset = -1;
+
+        if (mCurrentView == MONTH_AND_DAY_VIEW) {
+            listPosition = mDayPickerView.getMostVisiblePosition();
+        } else if (mCurrentView == YEAR_VIEW) {
+            listPosition = mYearPickerView.getFirstVisiblePosition();
+            listPositionOffset = mYearPickerView.getFirstPositionOffset();
+        }
+
+        return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
+                mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        mCurrentDate.set(ss.getSelectedDay(), ss.getSelectedMonth(), ss.getSelectedYear());
+        mCurrentView = ss.getCurrentView();
+        mMinDate.setTimeInMillis(ss.getMinDate());
+        mMaxDate.setTimeInMillis(ss.getMaxDate());
+
+        updateDisplay(false);
+        setCurrentView(mCurrentView);
+
+        final int listPosition = ss.getListPosition();
+        if (listPosition != -1) {
+            if (mCurrentView == MONTH_AND_DAY_VIEW) {
+                mDayPickerView.postSetSelection(listPosition);
+            } else if (mCurrentView == YEAR_VIEW) {
+                mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset());
+            }
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        onPopulateAccessibilityEvent(event);
+        return true;
+    }
+
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        event.getText().add(mCurrentDate.getTime().toString());
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        event.setClassName(DatePicker.class.getName());
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        info.setClassName(DatePicker.class.getName());
+    }
+
+    @Override
+    public void onYearSelected(int year) {
+        adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year);
+        mCurrentDate.set(Calendar.YEAR, year);
+        updatePickers();
+        setCurrentView(MONTH_AND_DAY_VIEW);
+        updateDisplay(true);
+        updateDoneButtonEnableState();
+    }
+
+    // If the newly selected month / year does not contain the currently selected day number,
+    // change the selected day number to the last day of the selected month or year.
+    //      e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
+    //      e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
+    private void adjustDayInMonthIfNeeded(int month, int year) {
+        int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
+        int daysInMonth = getDaysInMonth(month, year);
+        if (day > daysInMonth) {
+            mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
+        }
+    }
+
+    public static int getDaysInMonth(int month, int year) {
+        switch (month) {
+            case Calendar.JANUARY:
+            case Calendar.MARCH:
+            case Calendar.MAY:
+            case Calendar.JULY:
+            case Calendar.AUGUST:
+            case Calendar.OCTOBER:
+            case Calendar.DECEMBER:
+                return 31;
+            case Calendar.APRIL:
+            case Calendar.JUNE:
+            case Calendar.SEPTEMBER:
+            case Calendar.NOVEMBER:
+                return 30;
+            case Calendar.FEBRUARY:
+                return (year % 4 == 0) ? 29 : 28;
+            default:
+                throw new IllegalArgumentException("Invalid Month");
+        }
+    }
+
+    @Override
+    public void onDayOfMonthSelected(int year, int month, int day) {
+        mCurrentDate.set(Calendar.YEAR, year);
+        mCurrentDate.set(Calendar.MONTH, month);
+        mCurrentDate.set(Calendar.DAY_OF_MONTH, day);
+        updatePickers();
+        updateDisplay(true);
+        updateDoneButtonEnableState();
+    }
+
+    private void updateDoneButtonEnableState() {
+        if (mShowDoneButton) {
+            final boolean enabled = mCurrentDate.equals(mMinDate) ||
+                    mCurrentDate.equals(mMaxDate) ||
+                    (mCurrentDate.after(mMinDate) && mCurrentDate.before(mMaxDate));
+            mDoneButton.setEnabled(enabled);
+        }
+    }
+
+    private void updatePickers() {
+        Iterator<OnDateChangedListener> iterator = mListeners.iterator();
+        while (iterator.hasNext()) {
+            iterator.next().onDateChanged();
+        }
+    }
+
+    @Override
+    public void registerOnDateChangedListener(OnDateChangedListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public Calendar getSelectedDay() {
+        return mCurrentDate;
+    }
+
+    @Override
+    public void tryVibrate() {
+        mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
+    }
+
+    @Override
+    public void onClick(View v) {
+        tryVibrate();
+        if (v.getId() == R.id.date_picker_year) {
+            setCurrentView(YEAR_VIEW);
+        } else if (v.getId() == R.id.date_picker_month_and_day_layout) {
+            setCurrentView(MONTH_AND_DAY_VIEW);
+        }
+    }
+
+    /**
+     * Class for managing state storing/restoring.
+     */
+    private static class SavedState extends View.BaseSavedState {
+
+        private final int mSelectedYear;
+        private final int mSelectedMonth;
+        private final int mSelectedDay;
+        private final long mMinDate;
+        private final long mMaxDate;
+        private final int mCurrentView;
+        private final int mListPosition;
+        private final int mListPositionOffset;
+
+        /**
+         * Constructor called from {@link DatePicker#onSaveInstanceState()}
+         */
+        private SavedState(Parcelable superState, int year, int month, int day,
+                long minDate, long maxDate, int currentView, int listPosition,
+                int listPositionOffset) {
+            super(superState);
+            mSelectedYear = year;
+            mSelectedMonth = month;
+            mSelectedDay = day;
+            mMinDate = minDate;
+            mMaxDate = maxDate;
+            mCurrentView = currentView;
+            mListPosition = listPosition;
+            mListPositionOffset = listPositionOffset;
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            mSelectedYear = in.readInt();
+            mSelectedMonth = in.readInt();
+            mSelectedDay = in.readInt();
+            mMinDate = in.readLong();
+            mMaxDate = in.readLong();
+            mCurrentView = in.readInt();
+            mListPosition = in.readInt();
+            mListPositionOffset = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(mSelectedYear);
+            dest.writeInt(mSelectedMonth);
+            dest.writeInt(mSelectedDay);
+            dest.writeLong(mMinDate);
+            dest.writeLong(mMaxDate);
+            dest.writeInt(mCurrentView);
+            dest.writeInt(mListPosition);
+            dest.writeInt(mListPositionOffset);
+        }
+
+        public int getSelectedDay() {
+            return mSelectedDay;
+        }
+
+        public int getSelectedMonth() {
+            return mSelectedMonth;
+        }
+
+        public int getSelectedYear() {
+            return mSelectedYear;
+        }
+
+        public long getMinDate() {
+            return mMinDate;
+        }
+
+        public long getMaxDate() {
+            return mMaxDate;
+        }
+
+        public int getCurrentView() {
+            return mCurrentView;
+        }
+
+        public int getListPosition() {
+            return mListPosition;
+        }
+
+        public int getListPositionOffset() {
+            return mListPositionOffset;
+        }
+
+        @SuppressWarnings("all")
+        // suppress unused and hiding
+        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
+
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    /**
+     * Render an animator to pulsate a view in place.
+     * @param labelToAnimate the view to pulsate.
+     * @return The animator object. Use .start() to begin.
+     */
+    public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio,
+                                                  float increaseRatio) {
+        Keyframe k0 = Keyframe.ofFloat(0f, 1f);
+        Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio);
+        Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
+        Keyframe k3 = Keyframe.ofFloat(1f, 1f);
+
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X, k0, k1, k2, k3);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe(View.SCALE_Y, k0, k1, k2, k3);
+        ObjectAnimator pulseAnimator =
+                ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
+        pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
+
+        return pulseAnimator;
+    }
+}
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
new file mode 100644
index 0000000..c44bd46
--- /dev/null
+++ b/core/java/android/widget/DayPickerView.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * This displays a list of months in a calendar format with selectable days.
+ */
+class DayPickerView extends ListView implements AbsListView.OnScrollListener,
+        OnDateChangedListener {
+
+    private static final String TAG = "DayPickerView";
+
+    // How long the GoTo fling animation should last
+    private static final int GOTO_SCROLL_DURATION = 250;
+
+    // How long to wait after receiving an onScrollStateChanged notification before acting on it
+    private static final int SCROLL_CHANGE_DELAY = 40;
+
+    private static int LIST_TOP_OFFSET = -1; // so that the top line will be under the separator
+
+    private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
+
+    // These affect the scroll speed and feel
+    private float mFriction = 1.0f;
+
+    // highlighted time
+    private Calendar mSelectedDay = Calendar.getInstance();
+    private SimpleMonthAdapter mAdapter;
+
+    private Calendar mTempDay = Calendar.getInstance();
+
+    // which month should be displayed/highlighted [0-11]
+    private int mCurrentMonthDisplayed;
+    // used for tracking what state listview is in
+    private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+    // used for tracking what state listview is in
+    private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+    private DatePickerController mController;
+    private boolean mPerformingScroll;
+
+    private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(this);
+
+    public DayPickerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public DayPickerView(Context context, DatePickerController controller) {
+        super(context);
+        init();
+        setController(controller);
+    }
+
+    public void setController(DatePickerController controller) {
+        if (mController != null) {
+            mController.unregisterOnDateChangedListener(this);
+        }
+        mController = controller;
+        mController.registerOnDateChangedListener(this);
+        setUpAdapter();
+        setAdapter(mAdapter);
+        onDateChanged();
+    }
+
+    public void init() {
+        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        setDrawSelectorOnTop(false);
+
+        setUpListView();
+    }
+
+    public void onChange() {
+        setUpAdapter();
+        setAdapter(mAdapter);
+    }
+
+    /**
+     * Creates a new adapter if necessary and sets up its parameters. Override
+     * this method to provide a custom adapter.
+     */
+    protected void setUpAdapter() {
+        if (mAdapter == null) {
+            mAdapter = new SimpleMonthAdapter(getContext(), mController);
+        } else {
+            mAdapter.setSelectedDay(mSelectedDay);
+            mAdapter.notifyDataSetChanged();
+        }
+        // refresh the view with the new parameters
+        mAdapter.notifyDataSetChanged();
+    }
+
+    /*
+     * Sets all the required fields for the list view. Override this method to
+     * set a different list view behavior.
+     */
+    protected void setUpListView() {
+        // Transparent background on scroll
+        setCacheColorHint(0);
+        // No dividers
+        setDivider(null);
+        // Items are clickable
+        setItemsCanFocus(true);
+        // The thumb gets in the way, so disable it
+        setFastScrollEnabled(false);
+        setVerticalScrollBarEnabled(false);
+        setOnScrollListener(this);
+        setFadingEdgeLength(0);
+        // Make the scrolling behavior nicer
+        setFriction(ViewConfiguration.getScrollFriction() * mFriction);
+    }
+
+    private int getDiffMonths(Calendar start, Calendar end){
+        final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
+        final int diffMonths = end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
+        return diffMonths;
+    }
+
+    private int getPositionFromDay(Calendar day) {
+        final int diffMonthMax = getDiffMonths(mController.getMinDate(), mController.getMaxDate());
+        int diffMonth = getDiffMonths(mController.getMinDate(), day);
+
+        if (diffMonth < 0 ) {
+            diffMonth = 0;
+        } else if (diffMonth > diffMonthMax) {
+            diffMonth = diffMonthMax;
+        }
+
+        return diffMonth;
+    }
+
+    /**
+     * This moves to the specified time in the view. If the time is not already
+     * in range it will move the list so that the first of the month containing
+     * the time is at the top of the view. If the new time is already in view
+     * the list will not be scrolled unless forceScroll is true. This time may
+     * optionally be highlighted as selected as well.
+     *
+     * @param day The day to move to
+     * @param animate Whether to scroll to the given time or just redraw at the
+     *            new location
+     * @param setSelected Whether to set the given time as selected
+     * @param forceScroll Whether to recenter even if the time is already
+     *            visible
+     * @return Whether or not the view animated to the new location
+     */
+    public boolean goTo(Calendar day, boolean animate, boolean setSelected,
+                        boolean forceScroll) {
+
+        // Set the selected day
+        if (setSelected) {
+            mSelectedDay.setTimeInMillis(day.getTimeInMillis());
+        }
+
+        mTempDay.setTimeInMillis(day.getTimeInMillis());
+        final int position = getPositionFromDay(day);
+
+        View child;
+        int i = 0;
+        int top = 0;
+        // Find a child that's completely in the view
+        do {
+            child = getChildAt(i++);
+            if (child == null) {
+                break;
+            }
+            top = child.getTop();
+        } while (top < 0);
+
+        // Compute the first and last position visible
+        int selectedPosition;
+        if (child != null) {
+            selectedPosition = getPositionForView(child);
+        } else {
+            selectedPosition = 0;
+        }
+
+        if (setSelected) {
+            mAdapter.setSelectedDay(mSelectedDay);
+        }
+
+        // Check if the selected day is now outside of our visible range
+        // and if so scroll to the month that contains it
+        if (position != selectedPosition || forceScroll) {
+            setMonthDisplayed(mTempDay);
+            mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
+            if (animate) {
+                smoothScrollToPositionFromTop(
+                        position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
+                return true;
+            } else {
+                postSetSelection(position);
+            }
+        } else if (setSelected) {
+            setMonthDisplayed(mSelectedDay);
+        }
+        return false;
+    }
+
+    public void postSetSelection(final int position) {
+        clearFocus();
+        post(new Runnable() {
+
+            @Override
+            public void run() {
+                setSelection(position);
+            }
+        });
+        onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
+    }
+
+    /**
+     * Updates the title and selected month if the view has moved to a new
+     * month.
+     */
+    @Override
+    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                         int totalItemCount) {
+        SimpleMonthView child = (SimpleMonthView) view.getChildAt(0);
+        if (child == null) {
+            return;
+        }
+
+        mPreviousScrollState = mCurrentScrollState;
+    }
+
+    /**
+     * Sets the month displayed at the top of this view based on time. Override
+     * to add custom events when the title is changed.
+     */
+    protected void setMonthDisplayed(Calendar date) {
+        if (mCurrentMonthDisplayed != date.get(Calendar.MONTH)) {
+            mCurrentMonthDisplayed = date.get(Calendar.MONTH);
+            invalidateViews();
+        }
+    }
+
+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        // use a post to prevent re-entering onScrollStateChanged before it
+        // exits
+        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+    }
+
+    void setCalendarTextColor(ColorStateList colors) {
+        mAdapter.setCalendarTextColor(colors);
+    }
+
+    protected class ScrollStateRunnable implements Runnable {
+        private int mNewState;
+        private View mParent;
+
+        ScrollStateRunnable(View view) {
+            mParent = view;
+        }
+
+        /**
+         * Sets up the runnable with a short delay in case the scroll state
+         * immediately changes again.
+         *
+         * @param view The list view that changed state
+         * @param scrollState The new state it changed to
+         */
+        public void doScrollStateChange(AbsListView view, int scrollState) {
+            mParent.removeCallbacks(this);
+            mNewState = scrollState;
+            mParent.postDelayed(this, SCROLL_CHANGE_DELAY);
+        }
+
+        @Override
+        public void run() {
+            mCurrentScrollState = mNewState;
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG,
+                        "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
+            }
+            // Fix the position after a scroll or a fling ends
+            if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
+                    && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE
+                    && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+                mPreviousScrollState = mNewState;
+                int i = 0;
+                View child = getChildAt(i);
+                while (child != null && child.getBottom() <= 0) {
+                    child = getChildAt(++i);
+                }
+                if (child == null) {
+                    // The view is no longer visible, just return
+                    return;
+                }
+                int firstPosition = getFirstVisiblePosition();
+                int lastPosition = getLastVisiblePosition();
+                boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1;
+                final int top = child.getTop();
+                final int bottom = child.getBottom();
+                final int midpoint = getHeight() / 2;
+                if (scroll && top < LIST_TOP_OFFSET) {
+                    if (bottom > midpoint) {
+                        smoothScrollBy(top, GOTO_SCROLL_DURATION);
+                    } else {
+                        smoothScrollBy(bottom, GOTO_SCROLL_DURATION);
+                    }
+                }
+            } else {
+                mPreviousScrollState = mNewState;
+            }
+        }
+    }
+
+    /**
+     * Gets the position of the view that is most prominently displayed within the list view.
+     */
+    public int getMostVisiblePosition() {
+        final int firstPosition = getFirstVisiblePosition();
+        final int height = getHeight();
+
+        int maxDisplayedHeight = 0;
+        int mostVisibleIndex = 0;
+        int i=0;
+        int bottom = 0;
+        while (bottom < height) {
+            View child = getChildAt(i);
+            if (child == null) {
+                break;
+            }
+            bottom = child.getBottom();
+            int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop());
+            if (displayedHeight > maxDisplayedHeight) {
+                mostVisibleIndex = i;
+                maxDisplayedHeight = displayedHeight;
+            }
+            i++;
+        }
+        return firstPosition + mostVisibleIndex;
+    }
+
+    @Override
+    public void onDateChanged() {
+        goTo(mController.getSelectedDay(), false, true, true);
+    }
+
+    /**
+     * Attempts to return the date that has accessibility focus.
+     *
+     * @return The date that has accessibility focus, or {@code null} if no date
+     *         has focus.
+     */
+    private Calendar findAccessibilityFocus() {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child instanceof SimpleMonthView) {
+                final Calendar focus = ((SimpleMonthView) child).getAccessibilityFocus();
+                if (focus != null) {
+                    return focus;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Attempts to restore accessibility focus to a given date. No-op if
+     * {@code day} is {@code null}.
+     *
+     * @param day The date that should receive accessibility focus
+     * @return {@code true} if focus was restored
+     */
+    private boolean restoreAccessibilityFocus(Calendar day) {
+        if (day == null) {
+            return false;
+        }
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child instanceof SimpleMonthView) {
+                if (((SimpleMonthView) child).restoreAccessibilityFocus(day)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    protected void layoutChildren() {
+        final Calendar focusedDay = findAccessibilityFocus();
+        super.layoutChildren();
+        if (mPerformingScroll) {
+            mPerformingScroll = false;
+        } else {
+            restoreAccessibilityFocus(focusedDay);
+        }
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setItemCount(-1);
+    }
+
+    private String getMonthAndYearString(Calendar day) {
+        StringBuffer sbuf = new StringBuffer();
+        sbuf.append(day.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()));
+        sbuf.append(" ");
+        sbuf.append(mYearFormat.format(day.getTime()));
+        return sbuf.toString();
+    }
+
+    /**
+     * Necessary for accessibility, to ensure we support "scrolling" forward and backward
+     * in the month list.
+     */
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+        info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+    }
+
+    /**
+     * When scroll forward/backward events are received, announce the newly scrolled-to month.
+     */
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
+                action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+            return super.performAccessibilityAction(action, arguments);
+        }
+
+        // Figure out what month is showing.
+        int firstVisiblePosition = getFirstVisiblePosition();
+        int month = firstVisiblePosition % 12;
+        int year = firstVisiblePosition / 12 + mController.getMinYear();
+        Calendar day = Calendar.getInstance();
+        day.set(year, month, 1);
+
+        // Scroll either forward or backward one month.
+        if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
+            day.add(Calendar.MONTH, 1);
+            if (day.get(Calendar.MONTH) == 12) {
+                day.set(Calendar.MONTH, 0);
+                day.add(Calendar.YEAR, 1);
+            }
+        } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+            View firstVisibleView = getChildAt(0);
+            // If the view is fully visible, jump one month back. Otherwise, we'll just jump
+            // to the first day of first visible month.
+            if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
+                // There's an off-by-one somewhere, so the top of the first visible item will
+                // actually be -1 when it's at the exact top.
+                day.add(Calendar.MONTH, -1);
+                if (day.get(Calendar.MONTH) == -1) {
+                    day.set(Calendar.MONTH, 11);
+                    day.add(Calendar.YEAR, -1);
+                }
+            }
+        }
+
+        // Go to that month.
+        announceForAccessibility(getMonthAndYearString(day));
+        goTo(day, true, false, true);
+        mPerformingScroll = true;
+        return true;
+    }
+}
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 3758d86..57b8dcb 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -21,7 +21,6 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
-import android.graphics.RectF;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -106,7 +105,6 @@
     private float mPullDistance;
     
     private final Rect mBounds = new Rect();
-    private final RectF mArcRect = new RectF();
     private final Paint mPaint = new Paint();
     private float mRadius;
     private float mBaseGlowHeight;
@@ -318,11 +316,9 @@
 
         final int count = canvas.save();
 
-        final float y = mBounds.height();
-        final float centerY = y - mRadius;
         final float centerX = mBounds.centerX();
+        final float centerY = mBounds.height() - mRadius;
 
-        mArcRect.set(centerX - mRadius, centerY - mRadius, centerX + mRadius, centerY + mRadius);
         canvas.scale(1.f, Math.min(mGlowScaleY, 1.f), centerX, 0);
 
         final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
@@ -330,7 +326,7 @@
 
         canvas.clipRect(mBounds);
         canvas.translate(translateX, 0);
-        canvas.drawArc(mArcRect, 45, 90, false, mPaint);
+        canvas.drawCircle(centerX, centerY, mRadius, mPaint);
         canvas.restoreToCount(count);
 
         boolean oneLastFrame = false;
diff --git a/core/java/android/widget/OnDateChangedListener.java b/core/java/android/widget/OnDateChangedListener.java
new file mode 100644
index 0000000..29be888
--- /dev/null
+++ b/core/java/android/widget/OnDateChangedListener.java
@@ -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 android.widget;
+
+/**
+ * The callback used to notify other date picker components of a change in the selected date.
+ *
+ */
+interface OnDateChangedListener {
+
+    public void onDateChanged();
+}
+
diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java
new file mode 100644
index 0000000..53d0839
--- /dev/null
+++ b/core/java/android/widget/SimpleMonthAdapter.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Calendar;
+import java.util.HashMap;
+
+/**
+ * An adapter for a list of {@link android.widget.SimpleMonthView} items.
+ */
+class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayClickListener {
+    private static final String TAG = "SimpleMonthAdapter";
+
+    private final Context mContext;
+    private final DatePickerController mController;
+    private Calendar mSelectedDay;
+
+    private ColorStateList mCalendarTextColors;
+
+    public SimpleMonthAdapter(Context context, DatePickerController controller) {
+        mContext = context;
+        mController = controller;
+        init();
+        setSelectedDay(mController.getSelectedDay());
+    }
+
+    /**
+     * Updates the selected day and related parameters.
+     *
+     * @param day The day to highlight
+     */
+    public void setSelectedDay(Calendar day) {
+        if (mSelectedDay != day) {
+            mSelectedDay = day;
+            notifyDataSetChanged();
+        }
+    }
+
+    void setCalendarTextColor(ColorStateList colors) {
+        mCalendarTextColors = colors;
+    }
+
+    /**
+     * Set up the gesture detector and selected time
+     */
+    protected void init() {
+        mSelectedDay = Calendar.getInstance();
+    }
+
+    @Override
+    public int getCount() {
+        final int diffYear = mController.getMaxYear() - mController.getMinYear();
+        final int diffMonth = 1 + mController.getMaxMonth() - mController.getMinMonth()
+                + 12 * diffYear;
+        return diffMonth;
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return null;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        SimpleMonthView v;
+        HashMap<String, Integer> drawingParams = null;
+        if (convertView != null) {
+            v = (SimpleMonthView) convertView;
+            // We store the drawing parameters in the view so it can be recycled
+            drawingParams = (HashMap<String, Integer>) v.getTag();
+        } else {
+            v = new SimpleMonthView(mContext);
+            // Set up the new view
+            AbsListView.LayoutParams params = new AbsListView.LayoutParams(
+                    AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT);
+            v.setLayoutParams(params);
+            v.setClickable(true);
+            v.setOnDayClickListener(this);
+            if (mCalendarTextColors != null) {
+                v.setTextColor(mCalendarTextColors);
+            }
+        }
+        if (drawingParams == null) {
+            drawingParams = new HashMap<String, Integer>();
+        } else {
+            drawingParams.clear();
+        }
+        final int currentMonth = position + mController.getMinMonth();
+        final int month = currentMonth % 12;
+        final int year = currentMonth / 12 + mController.getMinYear();
+
+        int selectedDay = -1;
+        if (isSelectedDayInMonth(year, month)) {
+            selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH);
+        }
+
+        // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
+        // height/number of weeks before being displayed.
+        v.reuse();
+
+        final int enabledDayRangeStart;
+        if (mController.getMinMonth() == month && mController.getMinYear() == year) {
+            enabledDayRangeStart = mController.getMinDay();
+        } else {
+            enabledDayRangeStart = 1;
+        }
+
+        final int enabledDayRangeEnd;
+        if (mController.getMaxMonth() == month && mController.getMaxYear() == year) {
+            enabledDayRangeEnd = mController.getMaxDay();
+        } else {
+            enabledDayRangeEnd = 31;
+        }
+
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_YEAR, year);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_MONTH, month);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_ENABLEDDAYRANGE_START, enabledDayRangeStart);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_ENABLEDDAYRANGE_END, enabledDayRangeEnd);
+        v.setMonthParams(drawingParams);
+        v.invalidate();
+        return v;
+    }
+
+    private boolean isSelectedDayInMonth(int year, int month) {
+        return mSelectedDay.get(Calendar.YEAR) == year && mSelectedDay.get(Calendar.MONTH) == month;
+    }
+
+    @Override
+    public void onDayClick(SimpleMonthView view, Calendar day) {
+        if (day != null) {
+            onDayTapped(day);
+        }
+    }
+
+    /**
+     * Maintains the same hour/min/sec but moves the day to the tapped day.
+     *
+     * @param day The day that was tapped
+     */
+    protected void onDayTapped(Calendar day) {
+        mController.tryVibrate();
+        mController.onDayOfMonthSelected(day.get(Calendar.YEAR), day.get(Calendar.MONTH),
+                day.get(Calendar.DAY_OF_MONTH));
+        setSelectedDay(day);
+    }
+}
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
new file mode 100644
index 0000000..7589711
--- /dev/null
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
+
+import java.security.InvalidParameterException;
+import java.util.Calendar;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A calendar-like view displaying a specified month and the appropriate selectable day numbers
+ * within the specified month.
+ */
+class SimpleMonthView extends View {
+    private static final String TAG = "SimpleMonthView";
+
+    /**
+     * These params can be passed into the view to control how it appears.
+     * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
+     * values are unlikely to fit most layouts correctly.
+     */
+    /**
+     * This sets the height of this week in pixels
+     */
+    static final String VIEW_PARAMS_HEIGHT = "height";
+    /**
+     * This specifies the position (or weeks since the epoch) of this week,
+     * calculated using
+     */
+    static final String VIEW_PARAMS_MONTH = "month";
+    /**
+     * This specifies the position (or weeks since the epoch) of this week,
+     * calculated using
+     */
+    static final String VIEW_PARAMS_YEAR = "year";
+    /**
+     * This sets one of the days in this view as selected {@link Time#SUNDAY}
+     * through {@link Time#SATURDAY}.
+     */
+    static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
+    /**
+     * Which day the week should start on. {@link Time#SUNDAY} through
+     * {@link Time#SATURDAY}.
+     */
+    static final String VIEW_PARAMS_WEEK_START = "week_start";
+    /**
+     * First enabled day.
+     */
+    static final String VIEW_PARAMS_ENABLEDDAYRANGE_START = "enabled_day_range_start";
+    /**
+     * Last enabled day.
+     */
+    static final String VIEW_PARAMS_ENABLEDDAYRANGE_END = "enabled_day_range_end";
+
+    private static int DEFAULT_HEIGHT = 32;
+    private static int MIN_HEIGHT = 10;
+
+    private static final int DEFAULT_SELECTED_DAY = -1;
+    private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
+    private static final int DEFAULT_NUM_DAYS = 7;
+    private static final int DEFAULT_NUM_ROWS = 6;
+    private static final int MAX_NUM_ROWS = 6;
+
+    private static final int SELECTED_CIRCLE_ALPHA = 60;
+
+    private static int DAY_SEPARATOR_WIDTH = 1;
+
+    private int mMiniDayNumberTextSize;
+    private int mMonthLabelTextSize;
+    private int mMonthDayLabelTextSize;
+    private int mMonthHeaderSize;
+    private int mDaySelectedCircleSize;
+
+    // used for scaling to the device density
+    private static float mScale = 0;
+
+    // affects the padding on the sides of this view
+    private int mPadding = 0;
+
+    private String mDayOfWeekTypeface;
+    private String mMonthTitleTypeface;
+
+    private Paint mDayNumberPaint;
+    private Paint mDayNumberDisabledPaint;
+    private Paint mDayNumberSelectedPaint;
+
+    private Paint mMonthTitlePaint;
+    private Paint mMonthDayLabelPaint;
+
+    private final Formatter mFormatter;
+    private final StringBuilder mStringBuilder;
+
+    private int mMonth;
+    private int mYear;
+
+    // Quick reference to the width of this view, matches parent
+    private int mWidth;
+
+    // The height this view should draw at in pixels, set by height param
+    private int mRowHeight = DEFAULT_HEIGHT;
+
+    // If this view contains the today
+    private boolean mHasToday = false;
+
+    // Which day is selected [0-6] or -1 if no day is selected
+    private int mSelectedDay = -1;
+
+    // Which day is today [0-6] or -1 if no day is today
+    private int mToday = DEFAULT_SELECTED_DAY;
+
+    // Which day of the week to start on [0-6]
+    private int mWeekStart = DEFAULT_WEEK_START;
+
+    // How many days to display
+    private int mNumDays = DEFAULT_NUM_DAYS;
+
+    // The number of days + a spot for week number if it is displayed
+    private int mNumCells = mNumDays;
+
+    private int mDayOfWeekStart = 0;
+
+    // First enabled day
+    private int mEnabledDayStart = 1;
+
+    // Last enabled day
+    private int mEnabledDayEnd = 31;
+
+    private final Calendar mCalendar = Calendar.getInstance();
+    private final Calendar mDayLabelCalendar = Calendar.getInstance();
+
+    private final MonthViewTouchHelper mTouchHelper;
+
+    private int mNumRows = DEFAULT_NUM_ROWS;
+
+    // Optional listener for handling day click actions
+    private OnDayClickListener mOnDayClickListener;
+
+    // Whether to prevent setting the accessibility delegate
+    private boolean mLockAccessibilityDelegate;
+
+    private int mNormalTextColor;
+    private int mDisabledTextColor;
+    private int mSelectedDayColor;
+
+    public SimpleMonthView(Context context) {
+        this(context, null);
+    }
+
+    public SimpleMonthView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.datePickerStyle);
+    }
+
+    public SimpleMonthView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+
+        final Resources res = context.getResources();
+
+        mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
+        mMonthTitleTypeface = res.getString(R.string.sans_serif);
+
+        mStringBuilder = new StringBuilder(50);
+        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
+
+        mMiniDayNumberTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size);
+        mMonthLabelTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size);
+        mMonthDayLabelTextSize = res.getDimensionPixelSize(
+                R.dimen.datepicker_month_day_label_text_size);
+        mMonthHeaderSize = res.getDimensionPixelOffset(
+                R.dimen.datepicker_month_list_item_header_height);
+        mDaySelectedCircleSize = res.getDimensionPixelSize(
+                R.dimen.datepicker_day_number_select_circle_radius);
+
+        mRowHeight = (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height)
+                - mMonthHeaderSize) / MAX_NUM_ROWS;
+
+        // Set up accessibility components.
+        mTouchHelper = new MonthViewTouchHelper(this);
+        setAccessibilityDelegate(mTouchHelper);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+        mLockAccessibilityDelegate = true;
+
+        // Sets up any standard paints that will be used
+        initView();
+    }
+
+    void setTextColor(ColorStateList colors) {
+        final Resources res = getContext().getResources();
+
+        mNormalTextColor = colors.getColorForState(ENABLED_STATE_SET,
+                res.getColor(R.color.datepicker_default_normal_text_color_holo_light));
+        mMonthTitlePaint.setColor(mNormalTextColor);
+        mMonthDayLabelPaint.setColor(mNormalTextColor);
+
+        mDisabledTextColor = colors.getColorForState(EMPTY_STATE_SET,
+                res.getColor(R.color.datepicker_default_disabled_text_color_holo_light));
+        mDayNumberDisabledPaint.setColor(mDisabledTextColor);
+
+        mSelectedDayColor = colors.getColorForState(ENABLED_SELECTED_STATE_SET,
+                res.getColor(R.color.holo_blue_light));
+        mDayNumberSelectedPaint.setColor(mSelectedDayColor);
+        mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+    }
+
+    @Override
+    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+        // Workaround for a JB MR1 issue where accessibility delegates on
+        // top-level ListView items are overwritten.
+        if (!mLockAccessibilityDelegate) {
+            super.setAccessibilityDelegate(delegate);
+        }
+    }
+
+    public void setOnDayClickListener(OnDayClickListener listener) {
+        mOnDayClickListener = listener;
+    }
+
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        // First right-of-refusal goes the touch exploration helper.
+        if (mTouchHelper.dispatchHoverEvent(event)) {
+            return true;
+        }
+        return super.dispatchHoverEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_UP:
+                final int day = getDayFromLocation(event.getX(), event.getY());
+                if (day >= 0) {
+                    onDayClick(day);
+                }
+                break;
+        }
+        return true;
+    }
+
+    /**
+     * Sets up the text and style properties for painting.
+     */
+    private void initView() {
+        mMonthTitlePaint = new Paint();
+        mMonthTitlePaint.setAntiAlias(true);
+        mMonthTitlePaint.setColor(mNormalTextColor);
+        mMonthTitlePaint.setTextSize(mMonthLabelTextSize);
+        mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
+        mMonthTitlePaint.setTextAlign(Align.CENTER);
+        mMonthTitlePaint.setStyle(Style.FILL);
+        mMonthTitlePaint.setFakeBoldText(true);
+
+        mMonthDayLabelPaint = new Paint();
+        mMonthDayLabelPaint.setAntiAlias(true);
+        mMonthDayLabelPaint.setColor(mNormalTextColor);
+        mMonthDayLabelPaint.setTextSize(mMonthDayLabelTextSize);
+        mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
+        mMonthDayLabelPaint.setTextAlign(Align.CENTER);
+        mMonthDayLabelPaint.setStyle(Style.FILL);
+        mMonthDayLabelPaint.setFakeBoldText(true);
+
+        mDayNumberSelectedPaint = new Paint();
+        mDayNumberSelectedPaint.setAntiAlias(true);
+        mDayNumberSelectedPaint.setColor(mSelectedDayColor);
+        mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+        mDayNumberSelectedPaint.setTextAlign(Align.CENTER);
+        mDayNumberSelectedPaint.setStyle(Style.FILL);
+        mDayNumberSelectedPaint.setFakeBoldText(true);
+
+        mDayNumberPaint = new Paint();
+        mDayNumberPaint.setAntiAlias(true);
+        mDayNumberPaint.setTextSize(mMiniDayNumberTextSize);
+        mDayNumberPaint.setTextAlign(Align.CENTER);
+        mDayNumberPaint.setStyle(Style.FILL);
+        mDayNumberPaint.setFakeBoldText(false);
+
+        mDayNumberDisabledPaint = new Paint();
+        mDayNumberDisabledPaint.setAntiAlias(true);
+        mDayNumberDisabledPaint.setColor(mDisabledTextColor);
+        mDayNumberDisabledPaint.setTextSize(mMiniDayNumberTextSize);
+        mDayNumberDisabledPaint.setTextAlign(Align.CENTER);
+        mDayNumberDisabledPaint.setStyle(Style.FILL);
+        mDayNumberDisabledPaint.setFakeBoldText(false);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        drawMonthTitle(canvas);
+        drawWeekDayLabels(canvas);
+        drawDays(canvas);
+    }
+
+    /**
+     * Sets all the parameters for displaying this week. The only required
+     * parameter is the week number. Other parameters have a default value and
+     * will only update if a new value is included, except for focus month,
+     * which will always default to no focus month if no value is passed in. See
+     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
+     *
+     * @param params A map of the new parameters, see
+     *            {@link #VIEW_PARAMS_HEIGHT}
+     */
+    void setMonthParams(HashMap<String, Integer> params) {
+        if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
+            throw new InvalidParameterException(
+                    "You must specify the month and year for this view");
+        }
+        setTag(params);
+        // We keep the current value for any params not present
+        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+            mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
+            if (mRowHeight < MIN_HEIGHT) {
+                mRowHeight = MIN_HEIGHT;
+            }
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
+            mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
+        }
+
+        // Allocate space for caching the day numbers and focus values
+        mMonth = params.get(VIEW_PARAMS_MONTH);
+        mYear = params.get(VIEW_PARAMS_YEAR);
+
+        // Figure out what day today is
+        final Time today = new Time(Time.getCurrentTimezone());
+        today.setToNow();
+        mHasToday = false;
+        mToday = -1;
+
+        mCalendar.set(Calendar.MONTH, mMonth);
+        mCalendar.set(Calendar.YEAR, mYear);
+        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
+
+        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+        } else {
+            mWeekStart = mCalendar.getFirstDayOfWeek();
+        }
+
+        if (params.containsKey(VIEW_PARAMS_ENABLEDDAYRANGE_START)) {
+            mEnabledDayStart = params.get(VIEW_PARAMS_ENABLEDDAYRANGE_START);
+        }
+        if (params.containsKey(VIEW_PARAMS_ENABLEDDAYRANGE_END)) {
+            mEnabledDayEnd = params.get(VIEW_PARAMS_ENABLEDDAYRANGE_END);
+        }
+
+        mNumCells = getDaysInMonth(mMonth, mYear);
+        for (int i = 0; i < mNumCells; i++) {
+            final int day = i + 1;
+            if (sameDay(day, today)) {
+                mHasToday = true;
+                mToday = day;
+            }
+        }
+        mNumRows = calculateNumRows();
+
+        // Invalidate cached accessibility information.
+        mTouchHelper.invalidateRoot();
+    }
+
+    private static int getDaysInMonth(int month, int year) {
+        switch (month) {
+            case Calendar.JANUARY:
+            case Calendar.MARCH:
+            case Calendar.MAY:
+            case Calendar.JULY:
+            case Calendar.AUGUST:
+            case Calendar.OCTOBER:
+            case Calendar.DECEMBER:
+                return 31;
+            case Calendar.APRIL:
+            case Calendar.JUNE:
+            case Calendar.SEPTEMBER:
+            case Calendar.NOVEMBER:
+                return 30;
+            case Calendar.FEBRUARY:
+                return (year % 4 == 0) ? 29 : 28;
+            default:
+                throw new IllegalArgumentException("Invalid Month");
+        }
+    }
+
+    public void reuse() {
+        mNumRows = DEFAULT_NUM_ROWS;
+        requestLayout();
+    }
+
+    private int calculateNumRows() {
+        int offset = findDayOffset();
+        int dividend = (offset + mNumCells) / mNumDays;
+        int remainder = (offset + mNumCells) % mNumDays;
+        return (dividend + (remainder > 0 ? 1 : 0));
+    }
+
+    private boolean sameDay(int day, Time today) {
+        return mYear == today.year &&
+                mMonth == today.month &&
+                day == today.monthDay;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
+                + mMonthHeaderSize);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mWidth = w;
+
+        // Invalidate cached accessibility information.
+        mTouchHelper.invalidateRoot();
+    }
+
+    private String getMonthAndYearString() {
+        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
+                | DateUtils.FORMAT_NO_MONTH_DAY;
+        mStringBuilder.setLength(0);
+        long millis = mCalendar.getTimeInMillis();
+        return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags,
+                Time.getCurrentTimezone()).toString();
+    }
+
+    private void drawMonthTitle(Canvas canvas) {
+        int x = (mWidth + 2 * mPadding) / 2;
+        int y = (mMonthHeaderSize - mMonthDayLabelTextSize) / 2 + (mMonthLabelTextSize / 3);
+        canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
+    }
+
+    private void drawWeekDayLabels(Canvas canvas) {
+        int y = mMonthHeaderSize - (mMonthDayLabelTextSize / 2);
+        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+        for (int i = 0; i < mNumDays; i++) {
+            int calendarDay = (i + mWeekStart) % mNumDays;
+            int x = (2 * i + 1) * dayWidthHalf + mPadding;
+            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
+            canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
+                    Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
+                    mMonthDayLabelPaint);
+        }
+    }
+
+    /**
+     * Draws the month days.
+     */
+    private void drawDays(Canvas canvas) {
+        int y = (((mRowHeight + mMiniDayNumberTextSize) / 2) - DAY_SEPARATOR_WIDTH)
+                + mMonthHeaderSize;
+        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+        int j = findDayOffset();
+        for (int day = 1; day <= mNumCells; day++) {
+            int x = (2 * j + 1) * dayWidthHalf + mPadding;
+            if (mSelectedDay == day) {
+                canvas.drawCircle(x, y - (mMiniDayNumberTextSize / 3), mDaySelectedCircleSize,
+                        mDayNumberSelectedPaint);
+            }
+
+            if (mHasToday && mToday == day) {
+                mDayNumberPaint.setColor(mSelectedDayColor);
+            } else {
+                mDayNumberPaint.setColor(mNormalTextColor);
+            }
+            final Paint paint = (day < mEnabledDayStart || day > mEnabledDayEnd) ?
+                    mDayNumberDisabledPaint : mDayNumberPaint;
+            canvas.drawText(String.format("%d", day), x, y, paint);
+            j++;
+            if (j == mNumDays) {
+                j = 0;
+                y += mRowHeight;
+            }
+        }
+    }
+
+    private int findDayOffset() {
+        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
+                - mWeekStart;
+    }
+
+    /**
+     * Calculates the day that the given x position is in, accounting for week
+     * number. Returns the day or -1 if the position wasn't in a day.
+     *
+     * @param x The x position of the touch event
+     * @return The day number, or -1 if the position wasn't in a day
+     */
+    private int getDayFromLocation(float x, float y) {
+        int dayStart = mPadding;
+        if (x < dayStart || x > mWidth - mPadding) {
+            return -1;
+        }
+        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+        int row = (int) (y - mMonthHeaderSize) / mRowHeight;
+        int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
+
+        int day = column - findDayOffset() + 1;
+        day += row * mNumDays;
+        if (day < 1 || day > mNumCells) {
+            return -1;
+        }
+        return day;
+    }
+
+    /**
+     * Called when the user clicks on a day. Handles callbacks to the
+     * {@link OnDayClickListener} if one is set.
+     *
+     * @param day The day that was clicked
+     */
+    private void onDayClick(int day) {
+        if (mOnDayClickListener != null) {
+            Calendar date = Calendar.getInstance();
+            date.set(mYear, mMonth, day);
+            mOnDayClickListener.onDayClick(this, date);
+        }
+
+        // This is a no-op if accessibility is turned off.
+        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
+    }
+
+    /**
+     * @return The date that has accessibility focus, or {@code null} if no date
+     *         has focus
+     */
+    Calendar getAccessibilityFocus() {
+        final int day = mTouchHelper.getFocusedVirtualView();
+        Calendar date = null;
+        if (day >= 0) {
+            date = Calendar.getInstance();
+            date.set(mYear, mMonth, day);
+        }
+        return date;
+    }
+
+    /**
+     * Clears accessibility focus within the view. No-op if the view does not
+     * contain accessibility focus.
+     */
+    public void clearAccessibilityFocus() {
+        mTouchHelper.clearFocusedVirtualView();
+    }
+
+    /**
+     * Attempts to restore accessibility focus to the specified date.
+     *
+     * @param day The date which should receive focus
+     * @return {@code false} if the date is not valid for this month view, or
+     *         {@code true} if the date received focus
+     */
+    boolean restoreAccessibilityFocus(Calendar day) {
+        if ((day.get(Calendar.YEAR) != mYear) || (day.get(Calendar.MONTH) != mMonth) ||
+                (day.get(Calendar.DAY_OF_MONTH) > mNumCells)) {
+            return false;
+        }
+        mTouchHelper.setFocusedVirtualView(day.get(Calendar.DAY_OF_MONTH));
+        return true;
+    }
+
+    /**
+     * Provides a virtual view hierarchy for interfacing with an accessibility
+     * service.
+     */
+    private class MonthViewTouchHelper extends ExploreByTouchHelper {
+        private static final String DATE_FORMAT = "dd MMMM yyyy";
+
+        private final Rect mTempRect = new Rect();
+        private final Calendar mTempCalendar = Calendar.getInstance();
+
+        public MonthViewTouchHelper(View host) {
+            super(host);
+        }
+
+        public void setFocusedVirtualView(int virtualViewId) {
+            getAccessibilityNodeProvider(SimpleMonthView.this).performAction(
+                    virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+        }
+
+        public void clearFocusedVirtualView() {
+            final int focusedVirtualView = getFocusedVirtualView();
+            if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
+                getAccessibilityNodeProvider(SimpleMonthView.this).performAction(
+                        focusedVirtualView,
+                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+                        null);
+            }
+        }
+
+        @Override
+        protected int getVirtualViewAt(float x, float y) {
+            final int day = getDayFromLocation(x, y);
+            if (day >= 0) {
+                return day;
+            }
+            return ExploreByTouchHelper.INVALID_ID;
+        }
+
+        @Override
+        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+            for (int day = 1; day <= mNumCells; day++) {
+                virtualViewIds.add(day);
+            }
+        }
+
+        @Override
+        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+            event.setContentDescription(getItemDescription(virtualViewId));
+        }
+
+        @Override
+        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
+            getItemBounds(virtualViewId, mTempRect);
+
+            node.setContentDescription(getItemDescription(virtualViewId));
+            node.setBoundsInParent(mTempRect);
+            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+            if (virtualViewId == mSelectedDay) {
+                node.setSelected(true);
+            }
+
+        }
+
+        @Override
+        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+                Bundle arguments) {
+            switch (action) {
+                case AccessibilityNodeInfo.ACTION_CLICK:
+                    onDayClick(virtualViewId);
+                    return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Calculates the bounding rectangle of a given time object.
+         *
+         * @param day The day to calculate bounds for
+         * @param rect The rectangle in which to store the bounds
+         */
+        private void getItemBounds(int day, Rect rect) {
+            final int offsetX = mPadding;
+            final int offsetY = mMonthHeaderSize;
+            final int cellHeight = mRowHeight;
+            final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
+            final int index = ((day - 1) + findDayOffset());
+            final int row = (index / mNumDays);
+            final int column = (index % mNumDays);
+            final int x = (offsetX + (column * cellWidth));
+            final int y = (offsetY + (row * cellHeight));
+
+            rect.set(x, y, (x + cellWidth), (y + cellHeight));
+        }
+
+        /**
+         * Generates a description for a given time object. Since this
+         * description will be spoken, the components are ordered by descending
+         * specificity as DAY MONTH YEAR.
+         *
+         * @param day The day to generate a description for
+         * @return A description of the time object
+         */
+        private CharSequence getItemDescription(int day) {
+            mTempCalendar.set(mYear, mMonth, day);
+            final CharSequence date = DateFormat.format(DATE_FORMAT,
+                    mTempCalendar.getTimeInMillis());
+
+            if (day == mSelectedDay) {
+                return getContext().getString(R.string.item_is_selected, date);
+            }
+
+            return date;
+        }
+    }
+
+    /**
+     * Handles callbacks when the user clicks on a time object.
+     */
+    public interface OnDayClickListener {
+        public void onDayClick(SimpleMonthView view, Calendar day);
+    }
+}
diff --git a/core/java/android/widget/TextViewWithCircularIndicator.java b/core/java/android/widget/TextViewWithCircularIndicator.java
new file mode 100644
index 0000000..22d770c
--- /dev/null
+++ b/core/java/android/widget/TextViewWithCircularIndicator.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+class TextViewWithCircularIndicator extends TextView {
+
+    private static final int SELECTED_CIRCLE_ALPHA = 60;
+
+    private final Paint mCirclePaint = new Paint();
+
+    private final String mItemIsSelectedText;
+    private int mCircleColor;
+    private boolean mDrawIndicator;
+
+    public TextViewWithCircularIndicator(Context context) {
+        this(context, null);
+    }
+
+    public TextViewWithCircularIndicator(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TextViewWithCircularIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public TextViewWithCircularIndicator(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+
+        super(context, attrs);
+        Resources res = context.getResources();
+
+        // Use Theme attributes if possible
+        final TypedArray a = mContext.obtainStyledAttributes(attrs,
+                R.styleable.DatePicker, defStyleAttr, defStyleRes);
+
+        final int resId = a.getResourceId(
+                R.styleable.DatePicker_dateSelectorYearListItemTextAppearance, -1);
+        if (resId != -1) {
+            setTextAppearance(context, resId);
+        }
+
+        mItemIsSelectedText = res.getString(R.string.item_is_selected);
+
+        a.recycle();
+
+        init();
+    }
+
+    private void init() {
+        mCirclePaint.setTypeface(Typeface.create(mCirclePaint.getTypeface(), Typeface.BOLD));
+        mCirclePaint.setAntiAlias(true);
+        mCirclePaint.setTextAlign(Paint.Align.CENTER);
+        mCirclePaint.setStyle(Paint.Style.FILL);
+    }
+
+    public void setCircleColor(int color) {
+        if (color != mCircleColor) {
+            mCircleColor = color;
+            mCirclePaint.setColor(mCircleColor);
+            mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+            requestLayout();
+        }
+    }
+
+    public void setDrawIndicator(boolean drawIndicator) {
+        mDrawIndicator = drawIndicator;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mDrawIndicator) {
+            final int width = getWidth();
+            final int height = getHeight();
+            int radius = Math.min(width, height) / 2;
+            canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint);
+        }
+    }
+
+    @Override
+    public CharSequence getContentDescription() {
+        CharSequence itemText = getText();
+        if (mDrawIndicator) {
+            return String.format(mItemIsSelectedText, itemText);
+        } else {
+            return itemText;
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerDelegate.java
index bf3971c..cd89667 100644
--- a/core/java/android/widget/TimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerDelegate.java
@@ -1389,8 +1389,8 @@
         final Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
         final Keyframe k3 = Keyframe.ofFloat(1f, 1f);
 
-        PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3);
-        PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe("scaleY", k0, k1, k2, k3);
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X, k0, k1, k2, k3);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe(View.SCALE_Y, k0, k1, k2, k3);
         ObjectAnimator pulseAnimator =
                 ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
         pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
new file mode 100644
index 0000000..bac9320
--- /dev/null
+++ b/core/java/android/widget/YearPickerView.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.Calendar;
+
+import com.android.internal.R;
+
+/**
+ * Displays a selectable list of years.
+ */
+class YearPickerView extends ListView implements AdapterView.OnItemClickListener,
+        OnDateChangedListener {
+    private static final String TAG = "YearPickerView";
+
+    private DatePickerController mController;
+    private YearAdapter mAdapter;
+    private int mViewSize;
+    private int mChildSize;
+    private int mSelectedPosition = -1;
+    private int mYearSelectedCircleColor;
+
+    public YearPickerView(Context context) {
+        this(context, null);
+    }
+
+    public YearPickerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public YearPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public YearPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.WRAP_CONTENT);
+        setLayoutParams(frame);
+
+        Resources res = context.getResources();
+        mViewSize = res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height);
+        mChildSize = res.getDimensionPixelOffset(R.dimen.datepicker_year_label_height);
+
+        setVerticalFadingEdgeEnabled(true);
+        setFadingEdgeLength(mChildSize / 3);
+
+        final int paddingTop = res.getDimensionPixelSize(
+                R.dimen.datepicker_year_picker_padding_top);
+        setPadding(0, paddingTop, 0, 0);
+
+        // Use Theme attributes if possible
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.DatePicker, defStyleAttr, defStyleRes);
+
+        final int colorResId = a.getResourceId(
+                R.styleable.DatePicker_dateSelectorYearListSelectedCircleColor,
+                R.color.datepicker_default_circle_background_color_holo_light);
+        mYearSelectedCircleColor = res.getColor(colorResId);
+
+        a.recycle();
+
+        setOnItemClickListener(this);
+        setDividerHeight(0);
+    }
+
+    public void init(DatePickerController controller) {
+        mController = controller;
+        mController.registerOnDateChangedListener(this);
+
+        mAdapter = new YearAdapter(getContext(), R.layout.year_label_text_view);
+        updateAdapterData();
+        setAdapter(mAdapter);
+
+        onDateChanged();
+    }
+
+    public void setYearSelectedCircleColor(int color) {
+        if (color != mYearSelectedCircleColor) {
+            mYearSelectedCircleColor = color;
+        }
+        requestLayout();
+    }
+
+    public int getYearSelectedCircleColor()  {
+        return mYearSelectedCircleColor;
+    }
+
+    private void updateAdapterData() {
+        mAdapter.clear();
+        final int maxYear = mController.getMaxYear();
+        for (int year = mController.getMinYear(); year <= maxYear; year++) {
+            mAdapter.add(year);
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        mController.tryVibrate();
+        if (position != mSelectedPosition) {
+            mSelectedPosition = position;
+            mAdapter.notifyDataSetChanged();
+        }
+        mController.onYearSelected(mAdapter.getItem(position));
+    }
+
+    void setItemTextAppearance(int resId) {
+        mAdapter.setItemTextAppearance(resId);
+    }
+
+    private class YearAdapter extends ArrayAdapter<Integer> {
+        int mItemTextAppearanceResId;
+
+        public YearAdapter(Context context, int resource) {
+            super(context, resource);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            TextViewWithCircularIndicator v = (TextViewWithCircularIndicator)
+                    super.getView(position, convertView, parent);
+            v.setTextAppearance(getContext(), mItemTextAppearanceResId);
+            v.requestLayout();
+            int year = getItem(position);
+            boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year;
+            v.setDrawIndicator(selected);
+            if (selected) {
+                v.setCircleColor(mYearSelectedCircleColor);
+            }
+            return v;
+        }
+
+        public void setItemTextAppearance(int resId) {
+            mItemTextAppearanceResId = resId;
+        }
+    }
+
+    public void postSetSelectionCentered(final int position) {
+        postSetSelectionFromTop(position, mViewSize / 2 - mChildSize / 2);
+    }
+
+    public void postSetSelectionFromTop(final int position, final int offset) {
+        post(new Runnable() {
+
+            @Override
+            public void run() {
+                setSelectionFromTop(position, offset);
+                requestLayout();
+            }
+        });
+    }
+
+    public int getFirstPositionOffset() {
+        final View firstChild = getChildAt(0);
+        if (firstChild == null) {
+            return 0;
+        }
+        return firstChild.getTop();
+    }
+
+    @Override
+    public void onDateChanged() {
+        updateAdapterData();
+        mAdapter.notifyDataSetChanged();
+        postSetSelectionCentered(
+                mController.getSelectedDay().get(Calendar.YEAR) - mController.getMinYear());
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            event.setFromIndex(0);
+            event.setToIndex(0);
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 98a5843..901d6e6 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -47,6 +47,10 @@
     void noteProcessStart(String name, int uid);
     void noteProcessState(String name, int uid, int state);
     void noteProcessFinish(String name, int uid);
+    void noteSyncStart(String name, int uid);
+    void noteSyncFinish(String name, int uid);
+    void noteJobStart(String name, int uid);
+    void noteJobFinish(String name, int uid);
 
     void noteStartWakelock(int uid, int pid, String name, String historyName,
             int type, boolean unimportantForLogging);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6ca048e..dec94f2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -91,7 +91,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 109 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 112 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -2109,6 +2109,11 @@
             return;
         }
 
+        if (dataSize == 0) {
+            // The history is currently empty; we need it to start with a time stamp.
+            cur.currentTime = System.currentTimeMillis();
+            addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur);
+        }
         addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
     }
 
@@ -2362,6 +2367,50 @@
                 elapsedRealtime);
     }
 
+    public void noteSyncStartLocked(String name, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long uptime = SystemClock.uptimeMillis();
+        getUidStatsLocked(uid).noteStartSyncLocked(name, elapsedRealtime);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SYNC_START, name, uid);
+    }
+
+    public void noteSyncFinishLocked(String name, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long uptime = SystemClock.uptimeMillis();
+        getUidStatsLocked(uid).noteStopSyncLocked(name, elapsedRealtime);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SYNC_FINISH, name, uid);
+    }
+
+    public void noteJobStartLocked(String name, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long uptime = SystemClock.uptimeMillis();
+        getUidStatsLocked(uid).noteStartJobLocked(name, elapsedRealtime);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_START, name, uid);
+    }
+
+    public void noteJobFinishLocked(String name, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long uptime = SystemClock.uptimeMillis();
+        getUidStatsLocked(uid).noteStopJobLocked(name, elapsedRealtime);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid);
+    }
+
     private void requestWakelockCpuUpdate() {
         if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
             Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
@@ -3833,6 +3882,16 @@
         final ArrayMap<String, Wakelock> mWakelockStats = new ArrayMap<String, Wakelock>();
 
         /**
+         * The statistics we have collected for this uid's syncs.
+         */
+        final ArrayMap<String, StopwatchTimer> mSyncStats = new ArrayMap<String, StopwatchTimer>();
+
+        /**
+         * The statistics we have collected for this uid's jobs.
+         */
+        final ArrayMap<String, StopwatchTimer> mJobStats = new ArrayMap<String, StopwatchTimer>();
+
+        /**
          * The statistics we have collected for this uid's sensor activations.
          */
         final SparseArray<Sensor> mSensorStats = new SparseArray<Sensor>();
@@ -3872,6 +3931,16 @@
         }
 
         @Override
+        public Map<String, ? extends BatteryStats.Timer> getSyncStats() {
+            return mSyncStats;
+        }
+
+        @Override
+        public Map<String, ? extends BatteryStats.Timer> getJobStats() {
+            return mJobStats;
+        }
+
+        @Override
         public SparseArray<? extends BatteryStats.Uid.Sensor> getSensorStats() {
             return mSensorStats;
         }
@@ -4396,6 +4465,24 @@
                     active = true;
                 }
             }
+            for (int is=mSyncStats.size()-1; is>=0; is--) {
+                StopwatchTimer timer = mSyncStats.valueAt(is);
+                if (timer.reset(false)) {
+                    mSyncStats.removeAt(is);
+                    timer.detach();
+                } else {
+                    active = true;
+                }
+            }
+            for (int ij=mJobStats.size()-1; ij>=0; ij--) {
+                StopwatchTimer timer = mJobStats.valueAt(ij);
+                if (timer.reset(false)) {
+                    mJobStats.removeAt(ij);
+                    timer.detach();
+                } else {
+                    active = true;
+                }
+            }
             for (int ise=mSensorStats.size()-1; ise>=0; ise--) {
                 Sensor s = mSensorStats.valueAt(ise);
                 if (s.reset()) {
@@ -4497,6 +4584,22 @@
                 wakelock.writeToParcelLocked(out, elapsedRealtimeUs);
             }
 
+            int NS = mSyncStats.size();
+            out.writeInt(NS);
+            for (int is=0; is<NS; is++) {
+                out.writeString(mSyncStats.keyAt(is));
+                StopwatchTimer timer = mSyncStats.valueAt(is);
+                Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
+            }
+
+            int NJ = mJobStats.size();
+            out.writeInt(NJ);
+            for (int ij=0; ij<NJ; ij++) {
+                out.writeString(mJobStats.keyAt(ij));
+                StopwatchTimer timer = mJobStats.valueAt(ij);
+                Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
+            }
+
             int NSE = mSensorStats.size();
             out.writeInt(NSE);
             for (int ise=0; ise<NSE; ise++) {
@@ -4618,6 +4721,25 @@
                 mWakelockStats.put(wakelockName, wakelock);
             }
 
+            int numSyncs = in.readInt();
+            mSyncStats.clear();
+            for (int j = 0; j < numSyncs; j++) {
+                String syncName = in.readString();
+                if (in.readInt() != 0) {
+                    mSyncStats.put(syncName,
+                            new StopwatchTimer(Uid.this, SYNC, null, timeBase, in));
+                }
+            }
+
+            int numJobs = in.readInt();
+            mJobStats.clear();
+            for (int j = 0; j < numJobs; j++) {
+                String jobName = in.readString();
+                if (in.readInt() != 0) {
+                    mJobStats.put(jobName, new StopwatchTimer(Uid.this, JOB, null, timeBase, in));
+                }
+            }
+
             int numSensors = in.readInt();
             mSensorStats.clear();
             for (int k = 0; k < numSensors; k++) {
@@ -5670,6 +5792,38 @@
             return ss;
         }
 
+        public StopwatchTimer getSyncTimerLocked(String name) {
+            StopwatchTimer t = mSyncStats.get(name);
+            if (t == null) {
+                final int N = mSyncStats.size();
+                if (N > MAX_WAKELOCKS_PER_UID) {
+                    name = BATCHED_WAKELOCK_NAME;
+                    t = mSyncStats.get(name);
+                }
+                if (t == null) {
+                    t = new StopwatchTimer(Uid.this, SYNC, null, mOnBatteryTimeBase);
+                    mSyncStats.put(name, t);
+                }
+            }
+            return t;
+        }
+
+        public StopwatchTimer getJobTimerLocked(String name) {
+            StopwatchTimer t = mJobStats.get(name);
+            if (t == null) {
+                final int N = mJobStats.size();
+                if (N > MAX_WAKELOCKS_PER_UID) {
+                    name = BATCHED_WAKELOCK_NAME;
+                    t = mJobStats.get(name);
+                }
+                if (t == null) {
+                    t = new StopwatchTimer(Uid.this, JOB, null, mOnBatteryTimeBase);
+                    mJobStats.put(name, t);
+                }
+            }
+            return t;
+        }
+
         public StopwatchTimer getWakeTimerLocked(String name, int type) {
             Wakelock wl = mWakelockStats.get(name);
             if (wl == null) {
@@ -5737,6 +5891,34 @@
             return t;
         }
 
+        public void noteStartSyncLocked(String name, long elapsedRealtimeMs) {
+            StopwatchTimer t = getSyncTimerLocked(name);
+            if (t != null) {
+                t.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteStopSyncLocked(String name, long elapsedRealtimeMs) {
+            StopwatchTimer t = getSyncTimerLocked(name);
+            if (t != null) {
+                t.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteStartJobLocked(String name, long elapsedRealtimeMs) {
+            StopwatchTimer t = getJobTimerLocked(name);
+            if (t != null) {
+                t.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteStopJobLocked(String name, long elapsedRealtimeMs) {
+            StopwatchTimer t = getJobTimerLocked(name);
+            if (t != null) {
+                t.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
         public void noteStartWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
             StopwatchTimer t = getWakeTimerLocked(name, type);
             if (t != null) {
@@ -7170,7 +7352,7 @@
         // the last run until samples in this run.
         if (mHistoryBaseTime > 0) {
             long oldnow = SystemClock.elapsedRealtime();
-            mHistoryBaseTime = (mHistoryBaseTime - oldnow) + 60*1000;
+            mHistoryBaseTime = mHistoryBaseTime - oldnow + 1;
             if (DEBUG_HISTORY) {
                 StringBuilder sb = new StringBuilder(128);
                 sb.append("****************** ADJUSTED mHistoryBaseTime: ");
@@ -7433,6 +7615,26 @@
                 }
             }
 
+            int NS = in.readInt();
+            if (NS > 100) {
+                Slog.w(TAG, "File corrupt: too many syncs " + NS);
+                return;
+            }
+            for (int is = 0; is < NS; is++) {
+                String name = in.readString();
+                u.getSyncTimerLocked(name).readSummaryFromParcelLocked(in);
+            }
+
+            int NJ = in.readInt();
+            if (NJ > 100) {
+                Slog.w(TAG, "File corrupt: too many job timers " + NJ);
+                return;
+            }
+            for (int ij = 0; ij < NJ; ij++) {
+                String name = in.readString();
+                u.getJobTimerLocked(name).readSummaryFromParcelLocked(in);
+            }
+
             int NP = in.readInt();
             if (NP > 1000) {
                 Slog.w(TAG, "File corrupt: too many sensors " + NP);
@@ -7484,7 +7686,7 @@
                 String pkgName = in.readString();
                 Uid.Pkg p = u.getPackageStatsLocked(pkgName);
                 p.mWakeups = p.mLoadedWakeups = in.readInt();
-                final int NS = in.readInt();
+                NS = in.readInt();
                 if (NS > 1000) {
                     Slog.w(TAG, "File corrupt: too many services " + NS);
                     return;
@@ -7717,6 +7919,20 @@
                 }
             }
 
+            int NS = u.mSyncStats.size();
+            out.writeInt(NS);
+            for (int is=0; is<NS; is++) {
+                out.writeString(u.mSyncStats.keyAt(is));
+                u.mSyncStats.valueAt(is).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            }
+
+            int NJ = u.mJobStats.size();
+            out.writeInt(NJ);
+            for (int ij=0; ij<NJ; ij++) {
+                out.writeString(u.mJobStats.keyAt(ij));
+                u.mJobStats.valueAt(ij).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            }
+
             int NSE = u.mSensorStats.size();
             out.writeInt(NSE);
             for (int ise=0; ise<NSE; ise++) {
@@ -7760,7 +7976,7 @@
                     out.writeString(ent.getKey());
                     Uid.Pkg ps = ent.getValue();
                     out.writeInt(ps.mWakeups);
-                    final int NS = ps.mServiceStats.size();
+                    NS = ps.mServiceStats.size();
                     out.writeInt(NS);
                     if (NS > 0) {
                         for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg.Serv> sent
@@ -7786,7 +8002,7 @@
     void readFromParcelLocked(Parcel in) {
         int magic = in.readInt();
         if (magic != MAGIC) {
-            throw new ParcelFormatException("Bad magic number");
+            throw new ParcelFormatException("Bad magic number: #" + Integer.toHexString(magic));
         }
 
         readHistory(in, false);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 7f6159d..3ed4d51 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -16,10 +16,14 @@
 
 package com.android.internal.util;
 
+import android.util.ArraySet;
+
 import dalvik.system.VMRuntime;
+
 import libcore.util.EmptyArray;
 
 import java.lang.reflect.Array;
+import java.util.ArrayList;
 
 /**
  * ArrayUtils contains some methods that you can call to find out
@@ -332,4 +336,52 @@
     public static long[] cloneOrNull(long[] array) {
         return (array != null) ? array.clone() : null;
     }
+
+    public static <T> ArraySet<T> add(ArraySet<T> cur, T val) {
+        if (cur == null) {
+            cur = new ArraySet<>();
+        }
+        cur.add(val);
+        return cur;
+    }
+
+    public static <T> ArraySet<T> remove(ArraySet<T> cur, T val) {
+        if (cur == null) {
+            return null;
+        }
+        cur.remove(val);
+        if (cur.isEmpty()) {
+            return null;
+        } else {
+            return cur;
+        }
+    }
+
+    public static <T> boolean contains(ArraySet<T> cur, T val) {
+        return (cur != null) ? cur.contains(val) : false;
+    }
+
+    public static <T> ArrayList<T> add(ArrayList<T> cur, T val) {
+        if (cur == null) {
+            cur = new ArrayList<>();
+        }
+        cur.add(val);
+        return cur;
+    }
+
+    public static <T> ArrayList<T> remove(ArrayList<T> cur, T val) {
+        if (cur == null) {
+            return null;
+        }
+        cur.remove(val);
+        if (cur.isEmpty()) {
+            return null;
+        } else {
+            return cur;
+        }
+    }
+
+    public static <T> boolean contains(ArrayList<T> cur, T val) {
+        return (cur != null) ? cur.contains(val) : false;
+    }
 }
diff --git a/core/java/com/android/internal/widget/AccessibleDateAnimator.java b/core/java/com/android/internal/widget/AccessibleDateAnimator.java
new file mode 100644
index 0000000..e91a55c
--- /dev/null
+++ b/core/java/com/android/internal/widget/AccessibleDateAnimator.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.internal.widget;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ViewAnimator;
+
+/**
+ * @hide
+ */
+public class AccessibleDateAnimator extends ViewAnimator {
+    private long mDateMillis;
+
+    public AccessibleDateAnimator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setDateMillis(long dateMillis) {
+        mDateMillis = dateMillis;
+    }
+
+    /**
+     * Announce the currently-selected date when launched.
+     */
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+            // Clear the event's current text so that only the current date will be spoken.
+            event.getText().clear();
+            int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+                    DateUtils.FORMAT_SHOW_WEEKDAY;
+
+            String dateString = DateUtils.formatDateTime(getContext(), mDateMillis, flags);
+            event.getText().add(dateString);
+            return true;
+        }
+        return super.dispatchPopulateAccessibilityEvent(event);
+    }
+}
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
new file mode 100644
index 0000000..11c4ca1
--- /dev/null
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -0,0 +1,719 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.accessibility.*;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * ExploreByTouchHelper is a utility class for implementing accessibility
+ * support in custom {@link android.view.View}s that represent a collection of View-like
+ * logical items. It extends {@link android.view.accessibility.AccessibilityNodeProvider} and
+ * simplifies many aspects of providing information to accessibility services
+ * and managing accessibility focus. This class does not currently support
+ * hierarchies of logical items.
+ * <p>
+ * This should be applied to the parent view using
+ * {@link android.view.View#setAccessibilityDelegate}:
+ *
+ * <pre>
+ * mAccessHelper = ExploreByTouchHelper.create(someView, mAccessHelperCallback);
+ * ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
+ * </pre>
+ */
+public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
+    /** Virtual node identifier value for invalid nodes. */
+    public static final int INVALID_ID = Integer.MIN_VALUE;
+
+    /** Default class name used for virtual views. */
+    private static final String DEFAULT_CLASS_NAME = View.class.getName();
+
+    // Temporary, reusable data structures.
+    private final Rect mTempScreenRect = new Rect();
+    private final Rect mTempParentRect = new Rect();
+    private final Rect mTempVisibleRect = new Rect();
+    private final int[] mTempGlobalRect = new int[2];
+
+    /** View's context **/
+    private Context mContext;
+
+    /** System accessibility manager, used to check state and send events. */
+    private final AccessibilityManager mManager;
+
+    /** View whose internal structure is exposed through this helper. */
+    private final View mView;
+
+    /** Node provider that handles creating nodes and performing actions. */
+    private ExploreByTouchNodeProvider mNodeProvider;
+
+    /** Virtual view id for the currently focused logical item. */
+    private int mFocusedVirtualViewId = INVALID_ID;
+
+    /** Virtual view id for the currently hovered logical item. */
+    private int mHoveredVirtualViewId = INVALID_ID;
+
+    /**
+     * Factory method to create a new {@link ExploreByTouchHelper}.
+     *
+     * @param forView View whose logical children are exposed by this helper.
+     */
+    public ExploreByTouchHelper(View forView) {
+        if (forView == null) {
+            throw new IllegalArgumentException("View may not be null");
+        }
+
+        mView = forView;
+        mContext = forView.getContext();
+        mManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+    }
+
+    /**
+     * Returns the {@link android.view.accessibility.AccessibilityNodeProvider} for this helper.
+     *
+     * @param host View whose logical children are exposed by this helper.
+     * @return The accessibility node provider for this helper.
+     */
+    @Override
+    public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+        if (mNodeProvider == null) {
+            mNodeProvider = new ExploreByTouchNodeProvider();
+        }
+        return mNodeProvider;
+    }
+
+    /**
+     * Dispatches hover {@link android.view.MotionEvent}s to the virtual view hierarchy when
+     * the Explore by Touch feature is enabled.
+     * <p>
+     * This method should be called by overriding
+     * {@link View#dispatchHoverEvent}:
+     *
+     * <pre>&#64;Override
+     * public boolean dispatchHoverEvent(MotionEvent event) {
+     *   if (mHelper.dispatchHoverEvent(this, event) {
+     *     return true;
+     *   }
+     *   return super.dispatchHoverEvent(event);
+     * }
+     * </pre>
+     *
+     * @param event The hover event to dispatch to the virtual view hierarchy.
+     * @return Whether the hover event was handled.
+     */
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        if (!mManager.isEnabled() || !mManager.isTouchExplorationEnabled()) {
+            return false;
+        }
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_HOVER_MOVE:
+            case MotionEvent.ACTION_HOVER_ENTER:
+                final int virtualViewId = getVirtualViewAt(event.getX(), event.getY());
+                updateHoveredVirtualView(virtualViewId);
+                return (virtualViewId != INVALID_ID);
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mFocusedVirtualViewId != INVALID_ID) {
+                    updateHoveredVirtualView(INVALID_ID);
+                    return true;
+                }
+                return false;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Populates an event of the specified type with information about an item
+     * and attempts to send it up through the view hierarchy.
+     * <p>
+     * You should call this method after performing a user action that normally
+     * fires an accessibility event, such as clicking on an item.
+     *
+     * <pre>public void performItemClick(T item) {
+     *   ...
+     *   sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
+     * }
+     * </pre>
+     *
+     * @param virtualViewId The virtual view id for which to send an event.
+     * @param eventType The type of event to send.
+     * @return true if the event was sent successfully.
+     */
+    public boolean sendEventForVirtualView(int virtualViewId, int eventType) {
+        if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
+            return false;
+        }
+
+        final ViewParent parent = mView.getParent();
+        if (parent == null) {
+            return false;
+        }
+
+        final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+        return parent.requestSendAccessibilityEvent(mView, event);
+    }
+
+    /**
+     * Notifies the accessibility framework that the properties of the parent
+     * view have changed.
+     * <p>
+     * You <b>must</b> call this method after adding or removing items from the
+     * parent view.
+     */
+    public void invalidateRoot() {
+        invalidateVirtualView(View.NO_ID);
+    }
+
+    /**
+     * Notifies the accessibility framework that the properties of a particular
+     * item have changed.
+     * <p>
+     * You <b>must</b> call this method after changing any of the properties set
+     * in {@link #onPopulateNodeForVirtualView}.
+     *
+     * @param virtualViewId The virtual view id to invalidate.
+     */
+    public void invalidateVirtualView(int virtualViewId) {
+        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+    }
+
+    /**
+     * Returns the virtual view id for the currently focused item,
+     *
+     * @return A virtual view id, or {@link #INVALID_ID} if no item is
+     *         currently focused.
+     */
+    public int getFocusedVirtualView() {
+        return mFocusedVirtualViewId;
+    }
+
+    /**
+     * Sets the currently hovered item, sending hover accessibility events as
+     * necessary to maintain the correct state.
+     *
+     * @param virtualViewId The virtual view id for the item currently being
+     *            hovered, or {@link #INVALID_ID} if no item is hovered within
+     *            the parent view.
+     */
+    private void updateHoveredVirtualView(int virtualViewId) {
+        if (mHoveredVirtualViewId == virtualViewId) {
+            return;
+        }
+
+        final int previousVirtualViewId = mHoveredVirtualViewId;
+        mHoveredVirtualViewId = virtualViewId;
+
+        // Stay consistent with framework behavior by sending ENTER/EXIT pairs
+        // in reverse order. This is accurate as of API 18.
+        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+        sendEventForVirtualView(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+    }
+
+    /**
+     * Constructs and returns an {@link AccessibilityEvent} for the specified
+     * virtual view id, which includes the host view ({@link View#NO_ID}).
+     *
+     * @param virtualViewId The virtual view id for the item for which to
+     *            construct an event.
+     * @param eventType The type of event to construct.
+     * @return An {@link AccessibilityEvent} populated with information about
+     *         the specified item.
+     */
+    private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
+        switch (virtualViewId) {
+            case View.NO_ID:
+                return createEventForHost(eventType);
+            default:
+                return createEventForChild(virtualViewId, eventType);
+        }
+    }
+
+    /**
+     * Constructs and returns an {@link AccessibilityEvent} for the host node.
+     *
+     * @param eventType The type of event to construct.
+     * @return An {@link AccessibilityEvent} populated with information about
+     *         the specified item.
+     */
+    private AccessibilityEvent createEventForHost(int eventType) {
+        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+        onInitializeAccessibilityEvent(mView, event);
+        return event;
+    }
+
+    /**
+     * Constructs and returns an {@link AccessibilityEvent} populated with
+     * information about the specified item.
+     *
+     * @param virtualViewId The virtual view id for the item for which to
+     *            construct an event.
+     * @param eventType The type of event to construct.
+     * @return An {@link AccessibilityEvent} populated with information about
+     *         the specified item.
+     */
+    private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+        event.setEnabled(true);
+        event.setClassName(DEFAULT_CLASS_NAME);
+
+        // Allow the client to populate the event.
+        onPopulateEventForVirtualView(virtualViewId, event);
+
+        // Make sure the developer is following the rules.
+        if (event.getText().isEmpty() && (event.getContentDescription() == null)) {
+            throw new RuntimeException("Callbacks must add text or a content description in "
+                    + "populateEventForVirtualViewId()");
+        }
+
+        // Don't allow the client to override these properties.
+        event.setPackageName(mView.getContext().getPackageName());
+        event.setSource(mView, virtualViewId);
+
+        return event;
+    }
+
+    /**
+     * Constructs and returns an {@link android.view.accessibility.AccessibilityNodeInfo} for the
+     * specified virtual view id, which includes the host view
+     * ({@link View#NO_ID}).
+     *
+     * @param virtualViewId The virtual view id for the item for which to
+     *            construct a node.
+     * @return An {@link android.view.accessibility.AccessibilityNodeInfo} populated with information
+     *         about the specified item.
+     */
+    private AccessibilityNodeInfo createNode(int virtualViewId) {
+        switch (virtualViewId) {
+            case View.NO_ID:
+                return createNodeForHost();
+            default:
+                return createNodeForChild(virtualViewId);
+        }
+    }
+
+    /**
+     * Constructs and returns an {@link AccessibilityNodeInfo} for the
+     * host view populated with its virtual descendants.
+     *
+     * @return An {@link AccessibilityNodeInfo} for the parent node.
+     */
+    private AccessibilityNodeInfo createNodeForHost() {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView);
+        onInitializeAccessibilityNodeInfo(mView, node);
+
+        // Add the virtual descendants.
+        final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
+        getVisibleVirtualViews(virtualViewIds);
+
+        for (Integer childVirtualViewId : virtualViewIds) {
+            node.addChild(mView, childVirtualViewId);
+        }
+
+        return node;
+    }
+
+    /**
+     * Constructs and returns an {@link AccessibilityNodeInfo} for the
+     * specified item. Automatically manages accessibility focus actions.
+     * <p>
+     * Allows the implementing class to specify most node properties, but
+     * overrides the following:
+     * <ul>
+     * <li>{@link AccessibilityNodeInfo#setPackageName}
+     * <li>{@link AccessibilityNodeInfo#setClassName}
+     * <li>{@link AccessibilityNodeInfo#setParent(View)}
+     * <li>{@link AccessibilityNodeInfo#setSource(View, int)}
+     * <li>{@link AccessibilityNodeInfo#setVisibleToUser}
+     * <li>{@link AccessibilityNodeInfo#setBoundsInScreen(Rect)}
+     * </ul>
+     * <p>
+     * Uses the bounds of the parent view and the parent-relative bounding
+     * rectangle specified by
+     * {@link AccessibilityNodeInfo#getBoundsInParent} to automatically
+     * update the following properties:
+     * <ul>
+     * <li>{@link AccessibilityNodeInfo#setVisibleToUser}
+     * <li>{@link AccessibilityNodeInfo#setBoundsInParent}
+     * </ul>
+     *
+     * @param virtualViewId The virtual view id for item for which to construct
+     *            a node.
+     * @return An {@link AccessibilityNodeInfo} for the specified item.
+     */
+    private AccessibilityNodeInfo createNodeForChild(int virtualViewId) {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+
+        // Ensure the client has good defaults.
+        node.setEnabled(true);
+        node.setClassName(DEFAULT_CLASS_NAME);
+
+        // Allow the client to populate the node.
+        onPopulateNodeForVirtualView(virtualViewId, node);
+
+        // Make sure the developer is following the rules.
+        if ((node.getText() == null) && (node.getContentDescription() == null)) {
+            throw new RuntimeException("Callbacks must add text or a content description in "
+                    + "populateNodeForVirtualViewId()");
+        }
+
+        node.getBoundsInParent(mTempParentRect);
+        if (mTempParentRect.isEmpty()) {
+            throw new RuntimeException("Callbacks must set parent bounds in "
+                    + "populateNodeForVirtualViewId()");
+        }
+
+        final int actions = node.getActions();
+        if ((actions & AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) != 0) {
+            throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in "
+                    + "populateNodeForVirtualViewId()");
+        }
+        if ((actions & AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) != 0) {
+            throw new RuntimeException("Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in "
+                    + "populateNodeForVirtualViewId()");
+        }
+
+        // Don't allow the client to override these properties.
+        node.setPackageName(mView.getContext().getPackageName());
+        node.setSource(mView, virtualViewId);
+        node.setParent(mView);
+
+        // Manage internal accessibility focus state.
+        if (mFocusedVirtualViewId == virtualViewId) {
+            node.setAccessibilityFocused(true);
+            node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+        } else {
+            node.setAccessibilityFocused(false);
+            node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+        }
+
+        // Set the visibility based on the parent bound.
+        if (intersectVisibleToUser(mTempParentRect)) {
+            node.setVisibleToUser(true);
+            node.setBoundsInParent(mTempParentRect);
+        }
+
+        // Calculate screen-relative bound.
+        mView.getLocationOnScreen(mTempGlobalRect);
+        final int offsetX = mTempGlobalRect[0];
+        final int offsetY = mTempGlobalRect[1];
+        mTempScreenRect.set(mTempParentRect);
+        mTempScreenRect.offset(offsetX, offsetY);
+        node.setBoundsInScreen(mTempScreenRect);
+
+        return node;
+    }
+
+    private boolean performAction(int virtualViewId, int action, Bundle arguments) {
+        switch (virtualViewId) {
+            case View.NO_ID:
+                return performActionForHost(action, arguments);
+            default:
+                return performActionForChild(virtualViewId, action, arguments);
+        }
+    }
+
+    private boolean performActionForHost(int action, Bundle arguments) {
+        return performAccessibilityAction(mView, action, arguments);
+    }
+
+    private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+                return manageFocusForChild(virtualViewId, action, arguments);
+            default:
+                return onPerformActionForVirtualView(virtualViewId, action, arguments);
+        }
+    }
+
+    private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+                return requestAccessibilityFocus(virtualViewId);
+            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+                return clearAccessibilityFocus(virtualViewId);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Computes whether the specified {@link Rect} intersects with the visible
+     * portion of its parent {@link View}. Modifies {@code localRect} to contain
+     * only the visible portion.
+     *
+     * @param localRect A rectangle in local (parent) coordinates.
+     * @return Whether the specified {@link Rect} is visible on the screen.
+     */
+    private boolean intersectVisibleToUser(Rect localRect) {
+        // Missing or empty bounds mean this view is not visible.
+        if ((localRect == null) || localRect.isEmpty()) {
+            return false;
+        }
+
+        // Attached to invisible window means this view is not visible.
+        if (mView.getWindowVisibility() != View.VISIBLE) {
+            return false;
+        }
+
+        // An invisible predecessor means that this view is not visible.
+        ViewParent viewParent = mView.getParent();
+        while (viewParent instanceof View) {
+            final View view = (View) viewParent;
+            if ((view.getAlpha() <= 0) || (view.getVisibility() != View.VISIBLE)) {
+                return false;
+            }
+            viewParent = view.getParent();
+        }
+
+        // A null parent implies the view is not visible.
+        if (viewParent == null) {
+            return false;
+        }
+
+        // If no portion of the parent is visible, this view is not visible.
+        if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
+            return false;
+        }
+
+        // Check if the view intersects the visible portion of the parent.
+        return localRect.intersect(mTempVisibleRect);
+    }
+
+    /**
+     * Returns whether this virtual view is accessibility focused.
+     *
+     * @return True if the view is accessibility focused.
+     */
+    private boolean isAccessibilityFocused(int virtualViewId) {
+        return (mFocusedVirtualViewId == virtualViewId);
+    }
+
+    /**
+     * Attempts to give accessibility focus to a virtual view.
+     * <p>
+     * A virtual view will not actually take focus if
+     * {@link AccessibilityManager#isEnabled()} returns false,
+     * {@link AccessibilityManager#isTouchExplorationEnabled()} returns false,
+     * or the view already has accessibility focus.
+     *
+     * @param virtualViewId The id of the virtual view on which to place
+     *            accessibility focus.
+     * @return Whether this virtual view actually took accessibility focus.
+     */
+    private boolean requestAccessibilityFocus(int virtualViewId) {
+        final AccessibilityManager accessibilityManager =
+                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+        if (!mManager.isEnabled()
+                || !accessibilityManager.isTouchExplorationEnabled()) {
+            return false;
+        }
+        // TODO: Check virtual view visibility.
+        if (!isAccessibilityFocused(virtualViewId)) {
+            mFocusedVirtualViewId = virtualViewId;
+            // TODO: Only invalidate virtual view bounds.
+            mView.invalidate();
+            sendEventForVirtualView(virtualViewId,
+                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Attempts to clear accessibility focus from a virtual view.
+     *
+     * @param virtualViewId The id of the virtual view from which to clear
+     *            accessibility focus.
+     * @return Whether this virtual view actually cleared accessibility focus.
+     */
+    private boolean clearAccessibilityFocus(int virtualViewId) {
+        if (isAccessibilityFocused(virtualViewId)) {
+            mFocusedVirtualViewId = INVALID_ID;
+            mView.invalidate();
+            sendEventForVirtualView(virtualViewId,
+                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Provides a mapping between view-relative coordinates and logical
+     * items.
+     *
+     * @param x The view-relative x coordinate
+     * @param y The view-relative y coordinate
+     * @return virtual view identifier for the logical item under
+     *         coordinates (x,y)
+     */
+    protected abstract int getVirtualViewAt(float x, float y);
+
+    /**
+     * Populates a list with the view's visible items. The ordering of items
+     * within {@code virtualViewIds} specifies order of accessibility focus
+     * traversal.
+     *
+     * @param virtualViewIds The list to populate with visible items
+     */
+    protected abstract void getVisibleVirtualViews(List<Integer> virtualViewIds);
+
+    /**
+     * Populates an {@link AccessibilityEvent} with information about the
+     * specified item.
+     * <p>
+     * Implementations <b>must</b> populate the following required fields:
+     * <ul>
+     * <li>event text, see {@link AccessibilityEvent#getText} or
+     * {@link AccessibilityEvent#setContentDescription}
+     * </ul>
+     * <p>
+     * The helper class automatically populates the following fields with
+     * default values, but implementations may optionally override them:
+     * <ul>
+     * <li>item class name, set to android.view.View, see
+     * {@link AccessibilityEvent#setClassName}
+     * </ul>
+     * <p>
+     * The following required fields are automatically populated by the
+     * helper class and may not be overridden:
+     * <ul>
+     * <li>package name, set to the package of the host view's
+     * {@link Context}, see {@link AccessibilityEvent#setPackageName}
+     * <li>event source, set to the host view and virtual view identifier,
+     * see {@link AccessibilityRecord#setSource(View, int)}
+     * </ul>
+     *
+     * @param virtualViewId The virtual view id for the item for which to
+     *            populate the event
+     * @param event The event to populate
+     */
+    protected abstract void onPopulateEventForVirtualView(
+            int virtualViewId, AccessibilityEvent event);
+
+    /**
+     * Populates an {@link AccessibilityNodeInfo} with information
+     * about the specified item.
+     * <p>
+     * Implementations <b>must</b> populate the following required fields:
+     * <ul>
+     * <li>event text, see {@link AccessibilityNodeInfo#setText} or
+     * {@link AccessibilityNodeInfo#setContentDescription}
+     * <li>bounds in parent coordinates, see
+     * {@link AccessibilityNodeInfo#setBoundsInParent}
+     * </ul>
+     * <p>
+     * The helper class automatically populates the following fields with
+     * default values, but implementations may optionally override them:
+     * <ul>
+     * <li>enabled state, set to true, see
+     * {@link AccessibilityNodeInfo#setEnabled}
+     * <li>item class name, identical to the class name set by
+     * {@link #onPopulateEventForVirtualView}, see
+     * {@link AccessibilityNodeInfo#setClassName}
+     * </ul>
+     * <p>
+     * The following required fields are automatically populated by the
+     * helper class and may not be overridden:
+     * <ul>
+     * <li>package name, identical to the package name set by
+     * {@link #onPopulateEventForVirtualView}, see
+     * {@link AccessibilityNodeInfo#setPackageName}
+     * <li>node source, identical to the event source set in
+     * {@link #onPopulateEventForVirtualView}, see
+     * {@link AccessibilityNodeInfo#setSource(View, int)}
+     * <li>parent view, set to the host view, see
+     * {@link AccessibilityNodeInfo#setParent(View)}
+     * <li>visibility, computed based on parent-relative bounds, see
+     * {@link AccessibilityNodeInfo#setVisibleToUser}
+     * <li>accessibility focus, computed based on internal helper state, see
+     * {@link AccessibilityNodeInfo#setAccessibilityFocused}
+     * <li>bounds in screen coordinates, computed based on host view bounds,
+     * see {@link AccessibilityNodeInfo#setBoundsInScreen}
+     * </ul>
+     * <p>
+     * Additionally, the helper class automatically handles accessibility
+     * focus management by adding the appropriate
+     * {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS} or
+     * {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+     * action. Implementations must <b>never</b> manually add these actions.
+     * <p>
+     * The helper class also automatically modifies parent- and
+     * screen-relative bounds to reflect the portion of the item visible
+     * within its parent.
+     *
+     * @param virtualViewId The virtual view identifier of the item for
+     *            which to populate the node
+     * @param node The node to populate
+     */
+    protected abstract void onPopulateNodeForVirtualView(
+            int virtualViewId, AccessibilityNodeInfo node);
+
+    /**
+     * Performs the specified accessibility action on the item associated
+     * with the virtual view identifier. See
+     * {@link AccessibilityNodeInfo#performAction(int, Bundle)} for
+     * more information.
+     * <p>
+     * Implementations <b>must</b> handle any actions added manually in
+     * {@link #onPopulateNodeForVirtualView}.
+     * <p>
+     * The helper class automatically handles focus management resulting
+     * from {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}
+     * and
+     * {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+     * actions.
+     *
+     * @param virtualViewId The virtual view identifier of the item on which
+     *            to perform the action
+     * @param action The accessibility action to perform
+     * @param arguments (Optional) A bundle with additional arguments, or
+     *            null
+     * @return true if the action was performed
+     */
+    protected abstract boolean onPerformActionForVirtualView(
+            int virtualViewId, int action, Bundle arguments);
+
+    /**
+     * Exposes a virtual view hierarchy to the accessibility framework. Only
+     * used in API 16+.
+     */
+    private class ExploreByTouchNodeProvider extends AccessibilityNodeProvider {
+        @Override
+        public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+            return ExploreByTouchHelper.this.createNode(virtualViewId);
+        }
+
+        @Override
+        public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+            return ExploreByTouchHelper.this.performAction(virtualViewId, action, arguments);
+        }
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 0e22174..f65aab5 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -90,12 +90,12 @@
 	android_util_Process.cpp \
 	android_util_StringBlock.cpp \
 	android_util_XmlBlock.cpp \
+	android_graphics_Canvas.cpp \
 	android_graphics_Picture.cpp \
 	android/graphics/AutoDecodeCancel.cpp \
 	android/graphics/Bitmap.cpp \
 	android/graphics/BitmapFactory.cpp \
 	android/graphics/Camera.cpp \
-	android/graphics/Canvas.cpp \
 	android/graphics/CanvasProperty.cpp \
 	android/graphics/ColorFilter.cpp \
 	android/graphics/DrawFilter.cpp \
@@ -122,6 +122,7 @@
 	android/graphics/Rasterizer.cpp \
 	android/graphics/Region.cpp \
 	android/graphics/Shader.cpp \
+	android/graphics/SkiaCanvas.cpp \
 	android/graphics/SurfaceTexture.cpp \
 	android/graphics/Typeface.cpp \
 	android/graphics/TypefaceImpl.cpp \
diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp
index d17f46c..9f832b0 100644
--- a/core/jni/android/graphics/Camera.cpp
+++ b/core/jni/android/graphics/Camera.cpp
@@ -3,6 +3,7 @@
 
 #include "SkCamera.h"
 
+#include "Canvas.h"
 #include "GraphicsJNI.h"
 
 static jfieldID gNativeInstanceFieldID;
@@ -95,10 +96,10 @@
 }
 
 static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) {
-    SkCanvas* native_canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+    SkCanvas* canvas = reinterpret_cast<android::Canvas*>(canvasHandle)->getSkCanvas();
     jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID);
     Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle);
-    v->applyToCanvas((SkCanvas*)native_canvas);
+    v->applyToCanvas(canvas);
 }
 
 static jfloat Camera_dotWithNormal(JNIEnv* env, jobject obj,
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
deleted file mode 100644
index 6254f3d..0000000
--- a/core/jni/android/graphics/Canvas.cpp
+++ /dev/null
@@ -1,1330 +0,0 @@
-/*
- * Copyright (C) 2006-2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "SkCanvas.h"
-#include "SkClipStack.h"
-#include "SkDevice.h"
-#include "SkDeque.h"
-#include "SkDrawFilter.h"
-#include "SkGraphics.h"
-#include <SkImageInfo.h>
-#include "SkPorterDuff.h"
-#include "SkShader.h"
-#include "SkTArray.h"
-#include "SkTemplates.h"
-
-#include <minikin/Layout.h>
-#include "MinikinSkia.h"
-#include "MinikinUtils.h"
-
-#include "TypefaceImpl.h"
-
-#include "unicode/ubidi.h"
-#include "unicode/ushape.h"
-
-#include <utils/Log.h>
-
-namespace android {
-
-class ClipCopier : public SkCanvas::ClipVisitor {
-public:
-    ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {}
-
-    virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) {
-        m_dstCanvas->clipRect(rect, op, antialias);
-    }
-    virtual void clipRRect(const SkRRect& rrect, SkRegion::Op op, bool antialias) {
-        m_dstCanvas->clipRRect(rrect, op, antialias);
-    }
-    virtual void clipPath(const SkPath& path, SkRegion::Op op, bool antialias) {
-        m_dstCanvas->clipPath(path, op, antialias);
-    }
-
-private:
-    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();
-    return emptyClip ? JNI_FALSE : JNI_TRUE;
-}
-
-class SkCanvasGlue {
-public:
-    // Get the native wrapper for a given handle.
-    static inline NativeCanvasWrapper* getNativeWrapper(jlong nativeHandle) {
-        SkASSERT(nativeHandle);
-        return reinterpret_cast<NativeCanvasWrapper*>(nativeHandle);
-    }
-
-    // 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 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);
-    }
-
-    // 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()) {
-                ClipCopier copier(dstCanvas);
-                srcCanvas->replayClips(&copier);
-            }
-        }
-    }
-
-    // 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) {
-        SkGraphics::PurgeFontCache();
-    }
-
-    static void freeTextLayoutCaches(JNIEnv* env, jobject) {
-        Layout::purgeCaches();
-    }
-
-    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*, jobject, jlong canvasHandle) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        int width = canvas->getDevice()->accessBitmap(false).width();
-        return static_cast<jint>(width);
-    }
-
-    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 save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
-        NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
-        SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
-        return static_cast<jint>(wrapper->save(flags));
-    }
-
-    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);
-        SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
-        SkRect bounds;
-        bounds.set(l, t, r, b);
-        return static_cast<jint>(wrapper->saveLayer(&bounds, paint, flags));
-    }
-
-    static jint saveLayerAlpha(JNIEnv* env, jobject, jlong 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);
-        return static_cast<jint>(wrapper->saveLayerAlpha(&bounds, alpha, flags));
-    }
-
-    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;
-        }
-        wrapper->restore();
-    }
-
-    static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) {
-        return static_cast<jint>(getNativeCanvas(canvasHandle)->getSaveCount());
-    }
-
-    static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle,
-                               jint restoreCount) {
-        NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
-        if (restoreCount < 1) {
-            doThrowIAE(env, "Underflow in restoreToCount");
-            return;
-        }
-
-        while (wrapper->getCanvas()->getSaveCount() > restoreCount) {
-            wrapper->restore();
-        }
-    }
-
-    static void translate(JNIEnv*, jobject, jlong canvasHandle,
-                          jfloat dx, jfloat dy) {
-        getNativeCanvas(canvasHandle)->translate(dx, dy);
-    }
-
-    static void scale__FF(JNIEnv*, jobject, jlong canvasHandle,
-                          jfloat sx, jfloat sy) {
-        getNativeCanvas(canvasHandle)->scale(sx, sy);
-    }
-
-    static void rotate__F(JNIEnv*, jobject, jlong canvasHandle,
-                          jfloat degrees) {
-        getNativeCanvas(canvasHandle)->rotate(degrees);
-    }
-
-    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 = getNativeCanvas(canvasHandle);
-        const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
-        canvas->concat(*matrix);
-    }
-
-    static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle,
-                          jlong matrixHandle) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
-        if (NULL == matrix) {
-            canvas->resetMatrix();
-        } else {
-            canvas->setMatrix(*matrix);
-        }
-    }
-
-    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 = getNativeCanvas(canvasHandle);
-        c->clipRect(r, static_cast<SkRegion::Op>(op));
-        return hasNonEmptyClip(*c);
-    }
-
-    static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle,
-                             jlong pathHandle, jint op) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        canvas->clipPath(*reinterpret_cast<SkPath*>(pathHandle),
-                static_cast<SkRegion::Op>(op));
-        return hasNonEmptyClip(*canvas);
-    }
-
-    static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle,
-                               jlong deviceRgnHandle, jint op) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle);
-        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 = getNativeCanvas(canvasHandle);
-        canvas->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
-    }
-
-    static jboolean quickReject__Path(JNIEnv* env, jobject, jlong canvasHandle,
-                                       jlong pathHandle) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        bool result = canvas->quickReject(*reinterpret_cast<SkPath*>(pathHandle));
-        return result ? JNI_TRUE : JNI_FALSE;
-    }
-
-    static jboolean quickReject__FFFF(JNIEnv* env, jobject, jlong canvasHandle,
-                                       jfloat left, jfloat top, jfloat right,
-                                       jfloat bottom) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkRect r;
-        r.set(left, top, right, bottom);
-        bool result = canvas->quickReject(r);
-        return result ? JNI_TRUE : JNI_FALSE;
-    }
-
-    static void drawRGB(JNIEnv* env, jobject, jlong canvasHandle,
-                        jint r, jint g, jint b) {
-        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 = getNativeCanvas(canvasHandle);
-        canvas->drawARGB(a, r, g, b);
-    }
-
-    static void drawColor__I(JNIEnv* env, jobject, jlong canvasHandle,
-                             jint color) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        canvas->drawColor(color);
-    }
-
-    static void drawColor__II(JNIEnv* env, jobject, jlong canvasHandle,
-                              jint color, jint modeHandle) {
-        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 = getNativeCanvas(canvasHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        canvas->drawPaint(*paint);
-    }
-
-    static void doPoints(JNIEnv* env, jlong canvasHandle,
-                         jfloatArray jptsArray, jint offset, jint count,
-                         jlong paintHandle, jint modeHandle) {
-        NPE_CHECK_RETURN_VOID(env, jptsArray);
-        SkCanvas::PointMode mode = static_cast<SkCanvas::PointMode>(modeHandle);
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
-        AutoJavaFloatArray autoPts(env, jptsArray);
-        float* floats = autoPts.ptr();
-        const int length = autoPts.length();
-
-        if ((offset | count) < 0 || offset + count > length) {
-            doThrowAIOOBE(env);
-            return;
-        }
-
-        // now convert the floats into SkPoints
-        count >>= 1;    // now it is the number of points
-        SkAutoSTMalloc<32, SkPoint> storage(count);
-        SkPoint* pts = storage.get();
-        const float* src = floats + offset;
-        for (int i = 0; i < count; i++) {
-            pts[i].set(src[0], src[1]);
-            src += 2;
-        }
-        canvas->drawPoints(mode, count, pts, *paint);
-    }
-
-    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, jlong canvasHandle,
-                          jfloatArray jptsArray, jint offset, jint count,
-                          jlong paintHandle) {
-        doPoints(env, canvasHandle, jptsArray, offset, count, paintHandle,
-                 SkCanvas::kLines_PointMode);
-    }
-
-    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 = getNativeCanvas(canvasHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        canvas->drawLine(startX, startY, stopX, stopY, *paint);
-    }
-
-    static void drawRect__FFFFPaint(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);
-        canvas->drawRectCoords(left, top, right, bottom, *paint);
-    }
-
-    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 = 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 = getNativeCanvas(canvasHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        canvas->drawCircle(cx, cy, radius, *paint);
-    }
-
-    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 = 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 = getNativeCanvas(canvasHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
-        canvas->drawRoundRect(rect, rx, ry, *paint);
-    }
-
-    static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
-                         jlong paintHandle) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        canvas->drawPath(*path, *paint);
-    }
-
-    static void drawBitmap__BitmapFFPaint(JNIEnv* env, jobject jcanvas,
-                                          jlong canvasHandle, jlong bitmapHandle,
-                                          jfloat left, jfloat top,
-                                          jlong paintHandle, jint canvasDensity,
-                                          jint screenDensity, jint bitmapDensity) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
-        if (canvasDensity == bitmapDensity || canvasDensity == 0
-                || bitmapDensity == 0) {
-            if (screenDensity != 0 && screenDensity != bitmapDensity) {
-                SkPaint filteredPaint;
-                if (paint) {
-                    filteredPaint = *paint;
-                }
-                filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
-                canvas->drawBitmap(*bitmap, left, top, &filteredPaint);
-            } else {
-                canvas->drawBitmap(*bitmap, left, top, paint);
-            }
-        } else {
-            canvas->save();
-            SkScalar scale = canvasDensity / (float)bitmapDensity;
-            canvas->translate(left, top);
-            canvas->scale(scale, scale);
-
-            SkPaint filteredPaint;
-            if (paint) {
-                filteredPaint = *paint;
-            }
-            filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
-
-            canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
-
-            canvas->restore();
-        }
-    }
-
-    static void doDrawBitmap(JNIEnv* env, SkCanvas* canvas, SkBitmap* bitmap,
-                        jobject srcIRect, const SkRect& dst, SkPaint* paint,
-                        jint screenDensity, jint bitmapDensity) {
-        SkIRect    src, *srcPtr = NULL;
-
-        if (NULL != srcIRect) {
-            GraphicsJNI::jrect_to_irect(env, srcIRect, &src);
-            srcPtr = &src;
-        }
-
-        if (screenDensity != 0 && screenDensity != bitmapDensity) {
-            SkPaint filteredPaint;
-            if (paint) {
-                filteredPaint = *paint;
-            }
-            filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
-            canvas->drawBitmapRect(*bitmap, srcPtr, dst, &filteredPaint);
-        } else {
-            canvas->drawBitmapRect(*bitmap, srcPtr, dst, paint);
-        }
-    }
-
-    static void drawBitmapRF(JNIEnv* env, jobject, jlong canvasHandle,
-                             jlong bitmapHandle, jobject srcIRect,
-                             jobject dstRectF, jlong paintHandle,
-                             jint screenDensity, jint bitmapDensity) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        SkRect      dst;
-        GraphicsJNI::jrectf_to_rect(env, dstRectF, &dst);
-        doDrawBitmap(env, canvas, bitmap, srcIRect, dst, paint,
-                screenDensity, bitmapDensity);
-    }
-
-    static void drawBitmapRR(JNIEnv* env, jobject, jlong canvasHandle,
-                             jlong bitmapHandle, jobject srcIRect,
-                             jobject dstRect, jlong paintHandle,
-                             jint screenDensity, jint bitmapDensity) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        SkRect      dst;
-        GraphicsJNI::jrect_to_rect(env, dstRect, &dst);
-        doDrawBitmap(env, canvas, bitmap, srcIRect, dst, paint,
-                screenDensity, bitmapDensity);
-    }
-
-    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 = getNativeCanvas(canvasHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        // Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will
-        // correct the alphaType to kOpaque_SkAlphaType.
-        SkImageInfo info = SkImageInfo::Make(width, height,
-                               hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
-                               kPremul_SkAlphaType);
-        SkBitmap    bitmap;
-        if (!bitmap.allocPixels(info)) {
-            return;
-        }
-
-        if (!GraphicsJNI::SetPixels(env, jcolors, offset, stride,
-                0, 0, width, height, bitmap)) {
-            return;
-        }
-
-        canvas->drawBitmap(bitmap, x, y, paint);
-    }
-
-    static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle,
-                                 jlong bitmapHandle, jlong matrixHandle,
-                                 jlong paintHandle) {
-        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);
-        canvas->drawBitmapMatrix(*bitmap, *matrix, paint);
-    }
-
-    static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle,
-                          jlong bitmapHandle, jint meshWidth, jint meshHeight,
-                          jfloatArray jverts, jint vertIndex, jintArray jcolors,
-                          jint colorIndex, jlong paintHandle) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
-        const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
-        const int ptCount = (meshWidth + 1) * (meshHeight + 1);
-        const int indexCount = meshWidth * meshHeight * 6;
-
-        AutoJavaFloatArray  vertA(env, jverts, vertIndex + (ptCount << 1));
-        AutoJavaIntArray    colorA(env, jcolors, colorIndex + ptCount);
-
-        /*  Our temp storage holds 2 or 3 arrays.
-            texture points [ptCount * sizeof(SkPoint)]
-            optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
-                copy to convert from float to fixed
-            indices [ptCount * sizeof(uint16_t)]
-        */
-        ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
-        storageSize += indexCount * sizeof(uint16_t);  // indices[]
-
-        SkAutoMalloc storage(storageSize);
-        SkPoint* texs = (SkPoint*)storage.get();
-        SkPoint* verts;
-        uint16_t* indices;
-#ifdef SK_SCALAR_IS_FLOAT
-        verts = (SkPoint*)(vertA.ptr() + vertIndex);
-        indices = (uint16_t*)(texs + ptCount);
-#else
-        SkASSERT(false);
-#endif
-
-        // cons up texture coordinates and indices
-        {
-            const SkScalar w = SkIntToScalar(bitmap->width());
-            const SkScalar h = SkIntToScalar(bitmap->height());
-            const SkScalar dx = w / meshWidth;
-            const SkScalar dy = h / meshHeight;
-
-            SkPoint* texsPtr = texs;
-            SkScalar y = 0;
-            for (int i = 0; i <= meshHeight; i++) {
-                if (i == meshHeight) {
-                    y = h;  // to ensure numerically we hit h exactly
-                }
-                SkScalar x = 0;
-                for (int j = 0; j < meshWidth; j++) {
-                    texsPtr->set(x, y);
-                    texsPtr += 1;
-                    x += dx;
-                }
-                texsPtr->set(w, y);
-                texsPtr += 1;
-                y += dy;
-            }
-            SkASSERT(texsPtr - texs == ptCount);
-        }
-
-        // cons up indices
-        {
-            uint16_t* indexPtr = indices;
-            int index = 0;
-            for (int i = 0; i < meshHeight; i++) {
-                for (int j = 0; j < meshWidth; j++) {
-                    // lower-left triangle
-                    *indexPtr++ = index;
-                    *indexPtr++ = index + meshWidth + 1;
-                    *indexPtr++ = index + meshWidth + 2;
-                    // upper-right triangle
-                    *indexPtr++ = index;
-                    *indexPtr++ = index + meshWidth + 2;
-                    *indexPtr++ = index + 1;
-                    // bump to the next cell
-                    index += 1;
-                }
-                // bump to the next row
-                index += 1;
-            }
-            SkASSERT(indexPtr - indices == indexCount);
-            SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
-        }
-
-        // double-check that we have legal indices
-#ifdef SK_DEBUG
-        {
-            for (int i = 0; i < indexCount; i++) {
-                SkASSERT((unsigned)indices[i] < (unsigned)ptCount);
-            }
-        }
-#endif
-
-        // cons-up a shader for the bitmap
-        SkPaint tmpPaint;
-        if (paint) {
-            tmpPaint = *paint;
-        }
-        SkShader* shader = SkShader::CreateBitmapShader(*bitmap,
-                        SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
-        SkSafeUnref(tmpPaint.setShader(shader));
-
-        canvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, verts,
-                             texs, (const SkColor*)colorA.ptr(), NULL, indices,
-                             indexCount, tmpPaint);
-    }
-
-    static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
-                             jint modeHandle, jint vertexCount,
-                             jfloatArray jverts, jint vertIndex,
-                             jfloatArray jtexs, jint texIndex,
-                             jintArray jcolors, jint colorIndex,
-                             jshortArray jindices, jint indexIndex,
-                             jint indexCount, jlong paintHandle) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle);
-        const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
-        AutoJavaFloatArray  vertA(env, jverts, vertIndex + vertexCount);
-        AutoJavaFloatArray  texA(env, jtexs, texIndex + vertexCount);
-        AutoJavaIntArray    colorA(env, jcolors, colorIndex + vertexCount);
-        AutoJavaShortArray  indexA(env, jindices, indexIndex + indexCount);
-
-        const int ptCount = vertexCount >> 1;
-
-        SkPoint* verts;
-        SkPoint* texs = NULL;
-#ifdef SK_SCALAR_IS_FLOAT
-        verts = (SkPoint*)(vertA.ptr() + vertIndex);
-        if (jtexs != NULL) {
-            texs = (SkPoint*)(texA.ptr() + texIndex);
-        }
-#else
-        SkASSERT(false);
-#endif
-
-        const SkColor* colors = NULL;
-        const uint16_t* indices = NULL;
-        if (jcolors != NULL) {
-            colors = (const SkColor*)(colorA.ptr() + colorIndex);
-        }
-        if (jindices != NULL) {
-            indices = (const uint16_t*)(indexA.ptr() + indexIndex);
-        }
-
-        canvas->drawVertices(mode, ptCount, verts, texs, colors, NULL,
-                             indices, indexCount, *paint);
-    }
-
-
-    static void drawText___CIIFFIPaintTypeface(JNIEnv* env, jobject, jlong canvasHandle,
-                                               jcharArray text, jint index, jint count,
-                                               jfloat x, jfloat y, jint bidiFlags,
-                                               jlong paintHandle, jlong typefaceHandle) {
-        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, 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 bidiFlags,
-                                                   jlong paintHandle, jlong typefaceHandle) {
-        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, bidiFlags, paint, typeface);
-        env->ReleaseStringChars(text, textArray);
-    }
-
-    class DrawTextFunctor {
-    public:
-        DrawTextFunctor(const Layout& layout, SkCanvas* canvas, jfloat x, jfloat y, SkPaint* paint,
-                    uint16_t* glyphs, SkPoint* pos)
-                : layout(layout), canvas(canvas), x(x), y(y), paint(paint), glyphs(glyphs),
-                    pos(pos) { }
-
-        void operator()(size_t start, size_t end) {
-            for (size_t i = start; i < end; i++) {
-                glyphs[i] = layout.getGlyphId(i);
-                pos[i].fX = x + layout.getX(i);
-                pos[i].fY = y + layout.getY(i);
-            }
-            canvas->drawPosText(glyphs + start, (end - start) << 1, pos + start, *paint);
-        }
-    private:
-        const Layout& layout;
-        SkCanvas* canvas;
-        jfloat x;
-        jfloat y;
-        SkPaint* paint;
-        uint16_t* glyphs;
-        SkPoint* pos;
-    };
-
-    static void drawGlyphsToSkia(SkCanvas* canvas, SkPaint* paint, const Layout& layout, float x, float y) {
-        size_t nGlyphs = layout.nGlyphs();
-        uint16_t* glyphs = new uint16_t[nGlyphs];
-        SkPoint* pos = new SkPoint[nGlyphs];
-
-        x += MinikinUtils::xOffsetForTextAlign(paint, layout);
-        SkPaint::Align align = paint->getTextAlign();
-        paint->setTextAlign(SkPaint::kLeft_Align);
-        paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-        DrawTextFunctor f(layout, canvas, x, y, paint, glyphs, pos);
-        MinikinUtils::forFontRun(layout, paint, f);
-        doDrawTextDecorations(canvas, x, y, layout.getAdvance(), paint);
-        paint->setTextAlign(align);
-        delete[] glyphs;
-        delete[] pos;
-    }
-
-    static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
-            int start, int end,
-            jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
-
-        jint count = end - start;
-        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 bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
-
-        Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(textArray, start, count, contextCount, css);
-        drawGlyphsToSkia(canvas, paint, layout, x, y);
-    }
-
-// Same values used by Skia
-#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
-#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);
-            }
-            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 doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray,
-            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];
-            posPtr[indx].fY = y + posArray[indx * 2 + 1];
-        }
-        canvas->drawPosText(glyphArray, count << 1, posPtr, *paint);
-        delete[] posPtr;
-    }
-
-    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, bidiFlags, paint, typeface);
-        env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
-    }
-
-    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, 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 = getNativeCanvas(canvasHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        jchar* textArray = text ? env->GetCharArrayElements(text, NULL) : NULL;
-        jsize textCount = text ? env->GetArrayLength(text) : NULL;
-        float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
-        int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
-        SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
-        int indx;
-        for (indx = 0; indx < posCount; indx++) {
-            posPtr[indx].fX = posArray[indx << 1];
-            posPtr[indx].fY = posArray[(indx << 1) + 1];
-        }
-
-        SkPaint::TextEncoding encoding = paint->getTextEncoding();
-        paint->setTextEncoding(SkPaint::kUTF16_TextEncoding);
-        canvas->drawPosText(textArray + index, count << 1, posPtr, *paint);
-        paint->setTextEncoding(encoding);
-
-        if (text) {
-            env->ReleaseCharArrayElements(text, textArray, 0);
-        }
-        if (pos) {
-            env->ReleaseFloatArrayElements(pos, posArray, 0);
-        }
-        delete[] posPtr;
-    }
-
-    static void drawPosText__String_FPaint(JNIEnv* env, jobject,
-                                           jlong canvasHandle, jstring text,
-                                           jfloatArray pos,
-                                           jlong paintHandle) {
-        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;
-        float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
-        int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
-        SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
-
-        for (int indx = 0; indx < posCount; indx++) {
-            posPtr[indx].fX = posArray[indx << 1];
-            posPtr[indx].fY = posArray[(indx << 1) + 1];
-        }
-
-        SkPaint::TextEncoding encoding = paint->getTextEncoding();
-        paint->setTextEncoding(SkPaint::kUTF16_TextEncoding);
-        canvas->drawPosText(text_, byteLength << 1, posPtr, *paint);
-        paint->setTextEncoding(encoding);
-
-        if (text) {
-            env->ReleaseStringChars(text, (const jchar*) text_);
-        }
-        if (pos) {
-            env->ReleaseFloatArrayElements(pos, posArray, 0);
-        }
-        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,
-            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);
-        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,
-            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);
-        doDrawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
-                                   path, canvas, typeface);
-        env->ReleaseStringChars(text, text_);
-    }
-
-
-    // This function is a mirror of SkCanvas::getClipBounds except that it does
-    // not outset the edge of the clip to account for anti-aliasing. There is
-    // a skia bug to investigate pushing this logic into back into skia.
-    // (see https://code.google.com/p/skia/issues/detail?id=1303)
-    static bool getHardClipBounds(SkCanvas* canvas, SkRect* bounds) {
-        SkIRect ibounds;
-        if (!canvas->getClipDeviceBounds(&ibounds)) {
-            return false;
-        }
-
-        SkMatrix inverse;
-        // if we can't invert the CTM, we can't return local clip bounds
-        if (!canvas->getTotalMatrix().invert(&inverse)) {
-            if (bounds) {
-                bounds->setEmpty();
-            }
-            return false;
-        }
-
-        if (NULL != bounds) {
-            SkRect r = SkRect::Make(ibounds);
-            inverse.mapRect(bounds, r);
-        }
-        return true;
-    }
-
-    static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle,
-                                  jobject bounds) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkRect   r;
-        SkIRect ir;
-        bool result = getHardClipBounds(canvas, &r);
-
-        if (!result) {
-            r.setEmpty();
-        }
-        r.round(&ir);
-
-        (void)GraphicsJNI::irect_to_jrect(ir, env, bounds);
-        return result ? JNI_TRUE : JNI_FALSE;
-    }
-
-    static void getCTM(JNIEnv* env, jobject, jlong canvasHandle,
-                       jlong matrixHandle) {
-        SkCanvas* canvas = getNativeCanvas(canvasHandle);
-        SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
-        *matrix = canvas->getTotalMatrix();
-    }
-};
-
-static JNINativeMethod gCanvasMethods[] = {
-    {"finalizer", "(J)V", (void*) SkCanvasGlue::finalizer},
-    {"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},
-    {"native_clipRect","(JFFFFI)Z", (void*) SkCanvasGlue::clipRect},
-    {"native_clipPath","(JJI)Z", (void*) SkCanvasGlue::clipPath},
-    {"native_clipRegion","(JJI)Z", (void*) SkCanvasGlue::clipRegion},
-    {"nativeSetDrawFilter", "(JJ)V", (void*) SkCanvasGlue::setDrawFilter},
-    {"native_getClipBounds","(JLandroid/graphics/Rect;)Z",
-        (void*) SkCanvasGlue::getClipBounds},
-    {"native_getCTM", "(JJ)V", (void*)SkCanvasGlue::getCTM},
-    {"native_quickReject","(JJ)Z", (void*) SkCanvasGlue::quickReject__Path},
-    {"native_quickReject","(JFFFF)Z", (void*)SkCanvasGlue::quickReject__FFFF},
-    {"native_drawRGB","(JIII)V", (void*) SkCanvasGlue::drawRGB},
-    {"native_drawARGB","(JIIII)V", (void*) SkCanvasGlue::drawARGB},
-    {"native_drawColor","(JI)V", (void*) SkCanvasGlue::drawColor__I},
-    {"native_drawColor","(JII)V", (void*) SkCanvasGlue::drawColor__II},
-    {"native_drawPaint","(JJ)V", (void*) SkCanvasGlue::drawPaint},
-    {"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","(JFFFFJ)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
-    {"native_drawOval","(JFFFFJ)V", (void*) SkCanvasGlue::drawOval},
-    {"native_drawCircle","(JFFFJ)V", (void*) SkCanvasGlue::drawCircle},
-    {"native_drawArc","(JFFFFFFZJ)V", (void*) SkCanvasGlue::drawArc},
-    {"native_drawRoundRect","(JFFFFFFJ)V",
-        (void*) SkCanvasGlue::drawRoundRect},
-    {"native_drawPath","(JJJ)V", (void*) SkCanvasGlue::drawPath},
-    {"native_drawBitmap","(JJFFJIII)V",
-        (void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
-    {"native_drawBitmap","(JJLandroid/graphics/Rect;Landroid/graphics/RectF;JII)V",
-        (void*) SkCanvasGlue::drawBitmapRF},
-    {"native_drawBitmap","(JJLandroid/graphics/Rect;Landroid/graphics/Rect;JII)V",
-        (void*) SkCanvasGlue::drawBitmapRR},
-    {"native_drawBitmap", "(J[IIIFFIIZJ)V",
-    (void*)SkCanvasGlue::drawBitmapArray},
-    {"nativeDrawBitmapMatrix", "(JJJJ)V",
-        (void*)SkCanvasGlue::drawBitmapMatrix},
-    {"nativeDrawBitmapMesh", "(JJII[FI[IIJ)V",
-        (void*)SkCanvasGlue::drawBitmapMesh},
-    {"nativeDrawVertices", "(JII[FI[FI[II[SIIJ)V",
-        (void*)SkCanvasGlue::drawVertices},
-    {"native_drawText","(J[CIIFFIJJ)V",
-        (void*) SkCanvasGlue::drawText___CIIFFIPaintTypeface},
-    {"native_drawText","(JLjava/lang/String;IIFFIJJ)V",
-        (void*) SkCanvasGlue::drawText__StringIIFFIPaintTypeface},
-    {"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;JFFIJJ)V",
-        (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
-
-    {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches},
-
-    {"freeTextLayoutCaches", "()V", (void*) SkCanvasGlue::freeTextLayoutCaches}
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-#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_Canvas(JNIEnv* env) {
-    int result;
-
-    REG(env, "android/graphics/Canvas", gCanvasMethods);
-
-    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/Canvas.h b/core/jni/android/graphics/Canvas.h
new file mode 100644
index 0000000..710845d
--- /dev/null
+++ b/core/jni/android/graphics/Canvas.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CANVAS_H
+#define ANDROID_GRAPHICS_CANVAS_H
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkMatrix.h"
+
+namespace android {
+
+// TODO: move this further up the stack so that all interaction with minikin
+//       happens prior to calling into this interface
+class TypefaceImpl;
+
+class Canvas {
+public:
+    virtual ~Canvas() {};
+
+    static Canvas* create_canvas(SkBitmap* bitmap);
+    static Canvas* create_canvas(SkCanvas* skiaCanvas);
+
+    // TODO: enable HWUI to either create similar canvas wrapper or subclass
+    //       directly from Canvas
+    //static Canvas* create_canvas(uirenderer::Renderer* renderer);
+
+    // TODO: this is a temporary affordance until all necessary logic can be
+    //       moved within this interface! Further, the return value should
+    //       NOT be unref'd and is valid until this canvas is destroyed or a
+    //       new bitmap is set.
+    virtual SkCanvas* getSkCanvas() = 0;
+
+    virtual void setBitmap(SkBitmap* bitmap, bool copyState) = 0;
+
+    virtual bool isOpaque() = 0;
+    virtual int width() = 0;
+    virtual int height() = 0;
+
+// ----------------------------------------------------------------------------
+// Canvas state operations
+// ----------------------------------------------------------------------------
+    // Save (layer)
+    virtual int getSaveCount() const = 0;
+    virtual int save(SkCanvas::SaveFlags flags) = 0;
+    virtual void restore() = 0;
+    virtual void restoreToCount(int saveCount) = 0;
+
+    virtual int saveLayer(float left, float top, float right, float bottom,
+                const SkPaint* paint, SkCanvas::SaveFlags flags) = 0;
+    virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+            int alpha, SkCanvas::SaveFlags flags) = 0;
+
+    // Matrix
+    virtual void getMatrix(SkMatrix* outMatrix) const = 0;
+    virtual void setMatrix(const SkMatrix& matrix) = 0;
+
+    virtual void concat(const SkMatrix& matrix) = 0;
+    virtual void rotate(float degrees) = 0;
+    virtual void scale(float sx, float sy) = 0;
+    virtual void skew(float sx, float sy) = 0;
+    virtual void translate(float dx, float dy) = 0;
+
+    // clip
+    virtual bool getClipBounds(SkRect* outRect) const = 0;
+    virtual bool quickRejectRect(float left, float top, float right, float bottom) const = 0;
+    virtual bool quickRejectPath(const SkPath& path) const = 0;
+
+    virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) = 0;
+    virtual bool clipPath(const SkPath* path, SkRegion::Op op) = 0;
+    virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) = 0;
+
+    // filters
+    virtual void setDrawFilter(SkDrawFilter* drawFilter) = 0;
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations
+// ----------------------------------------------------------------------------
+    virtual void drawColor(int color, SkXfermode::Mode mode) = 0;
+    virtual void drawPaint(const SkPaint& paint) = 0;
+
+    // Geometry
+    virtual void drawPoint(float x, float y, const SkPaint& paint) = 0;
+    virtual void drawPoints(const float* points, int count, const SkPaint& paint) = 0;
+    virtual void drawLine(float startX, float startY, float stopX, float stopY,
+                const SkPaint& paint) = 0;
+    virtual void drawLines(const float* points, int count, const SkPaint& paint) = 0;
+    virtual void drawRect(float left, float top, float right, float bottom,
+            const SkPaint& paint) = 0;
+    virtual void drawRoundRect(float left, float top, float right, float bottom,
+            float rx, float ry, const SkPaint& paint) = 0;
+    virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) = 0;
+    virtual void drawOval(float left, float top, float right, float bottom,
+            const SkPaint& paint) = 0;
+    virtual void drawArc(float left, float top, float right, float bottom,
+            float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) = 0;
+    virtual void drawPath(const SkPath& path, const SkPaint& paint) = 0;
+    virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+                              const float* verts, const float* tex, const int* colors,
+                              const uint16_t* indices, int indexCount, const SkPaint& paint) = 0;
+
+    // Bitmap-based
+    virtual void drawBitmap(const SkBitmap& bitmap, float left, float top,
+            const SkPaint* paint) = 0;
+    virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+            const SkPaint* paint) = 0;
+    virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop,
+            float dstRight, float dstBottom, const SkPaint* paint) = 0;
+    virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+            const float* vertices, const int* colors, const SkPaint* paint) = 0;
+
+    // Text
+    virtual void drawText(const char* text, int start, int count, int contextCount,
+            float x, float y, int bidiFlags, const SkPaint& paint,
+            TypefaceImpl* typeface) = 0;
+    virtual void drawPosText(const char* text, const float* positions, int count, int posCount,
+            const SkPaint& paint) = 0;
+    virtual void drawTextOnPath(const char* text, int count, const SkPath& path,
+            float hOffset, float vOffset, const SkPaint& paint) = 0;
+};
+
+}; // namespace android
+#endif // ANDROID_GRAPHICS_CANVAS_H
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 5cc2b95..74be577 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -4,6 +4,7 @@
 #include "JNIHelp.h"
 #include "GraphicsJNI.h"
 
+#include "Canvas.h"
 #include "SkCanvas.h"
 #include "SkDevice.h"
 #include "SkMath.h"
@@ -364,7 +365,7 @@
     SkASSERT(canvas);
     SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
     jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID);
-    SkCanvas* c = getNativeCanvas(canvasHandle);
+    SkCanvas* c = reinterpret_cast<android::Canvas*>(canvasHandle)->getSkCanvas();
     SkASSERT(c);
     return c;
 }
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 8150edf..28a6edb 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -47,7 +47,6 @@
     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);
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index ab5bdb0..e82e8a6 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -23,6 +23,7 @@
 
 #include <Caches.h>
 
+#include "Canvas.h"
 #include "SkCanvas.h"
 #include "SkRegion.h"
 #include "GraphicsJNI.h"
@@ -119,7 +120,7 @@
     static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF,
             jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
             jint destDensity, jint srcDensity) {
-        SkCanvas* canvas       = GraphicsJNI::getNativeCanvas(canvasHandle);
+        SkCanvas* canvas       = reinterpret_cast<Canvas*>(canvasHandle)->getSkCanvas();
         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 +139,7 @@
     static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect,
             jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
             jint destDensity, jint srcDensity) {
-        SkCanvas* canvas       = GraphicsJNI::getNativeCanvas(canvasHandle);
+        SkCanvas* canvas       = reinterpret_cast<Canvas*>(canvasHandle)->getSkCanvas();
         const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         Res_png_9patch* chunk  = reinterpret_cast<Res_png_9patch*>(chunkHandle);
         const SkPaint* paint   = reinterpret_cast<SkPaint*>(paintHandle);
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index bc0c25f..d214575 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
+#include "Canvas.h"
 #include "Picture.h"
 
-#include "SkCanvas.h"
 #include "SkStream.h"
 
 namespace android {
@@ -36,12 +36,13 @@
     }
 }
 
-SkCanvas* Picture::beginRecording(int width, int height) {
+Canvas* Picture::beginRecording(int width, int height) {
     mPicture.reset(NULL);
     mRecorder.reset(new SkPictureRecorder);
     mWidth = width;
     mHeight = height;
-    return mRecorder->beginRecording(width, height, NULL, 0);
+    SkCanvas* canvas = mRecorder->beginRecording(width, height, NULL, 0);
+    return Canvas::create_canvas(canvas);
 }
 
 void Picture::endRecording() {
@@ -93,14 +94,14 @@
     }
 }
 
-void Picture::draw(SkCanvas* canvas) {
+void Picture::draw(Canvas* 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);
+        const_cast<SkPicture*>(mPicture.get())->draw(canvas->getSkCanvas());
     }
 }
 
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
index abb0403..a2e5d4a 100644
--- a/core/jni/android/graphics/Picture.h
+++ b/core/jni/android/graphics/Picture.h
@@ -22,14 +22,13 @@
 #include "SkRefCnt.h"
 #include "SkTemplates.h"
 
-class SkCanvas;
-class SkPicture;
-class SkPictureRecorder;
 class SkStream;
 class SkWStream;
 
 namespace android {
 
+class Canvas;
+
 // 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
@@ -38,7 +37,7 @@
 public:
     explicit Picture(const Picture* src = NULL);
 
-    SkCanvas* beginRecording(int width, int height);
+    Canvas* beginRecording(int width, int height);
 
     void endRecording();
 
@@ -50,7 +49,7 @@
 
     void serialize(SkWStream* stream) const;
 
-    void draw(SkCanvas* canvas);
+    void draw(Canvas* canvas);
 
 private:
     int mWidth;
diff --git a/core/jni/android/graphics/SkiaCanvas.cpp b/core/jni/android/graphics/SkiaCanvas.cpp
new file mode 100644
index 0000000..5e93313
--- /dev/null
+++ b/core/jni/android/graphics/SkiaCanvas.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "Canvas.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkCanvas.h"
+#include "SkClipStack.h"
+#include "SkDevice.h"
+#include "SkDeque.h"
+#include "SkDrawFilter.h"
+#include "SkGraphics.h"
+#include "SkPorterDuff.h"
+#include "SkShader.h"
+#include "SkTArray.h"
+#include "SkTemplates.h"
+
+#include <minikin/Layout.h>
+#include "MinikinSkia.h"
+#include "MinikinUtils.h"
+
+#include "TypefaceImpl.h"
+
+#include "unicode/ubidi.h"
+#include "unicode/ushape.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+// Holds an SkCanvas reference plus additional native data.
+class SkiaCanvas : public Canvas {
+public:
+    SkiaCanvas(SkBitmap* bitmap);
+
+    SkiaCanvas(SkCanvas* canvas) : mCanvas(canvas) {
+        SkASSERT(canvas);
+    }
+
+    virtual SkCanvas* getSkCanvas() {
+        return mCanvas.get();
+    }
+
+    virtual void setBitmap(SkBitmap* bitmap, bool copyState);
+
+    virtual bool isOpaque();
+    virtual int width();
+    virtual int height();
+
+    virtual int getSaveCount() const;
+    virtual int save(SkCanvas::SaveFlags flags);
+    virtual void restore();
+    virtual void restoreToCount(int saveCount);
+
+    virtual int saveLayer(float left, float top, float right, float bottom,
+                const SkPaint* paint, SkCanvas::SaveFlags flags);
+    virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+            int alpha, SkCanvas::SaveFlags flags);
+
+    virtual void getMatrix(SkMatrix* outMatrix) const;
+    virtual void setMatrix(const SkMatrix& matrix);
+    virtual void concat(const SkMatrix& matrix);
+    virtual void rotate(float degrees);
+    virtual void scale(float sx, float sy);
+    virtual void skew(float sx, float sy);
+    virtual void translate(float dx, float dy);
+
+    virtual bool getClipBounds(SkRect* outRect) const;
+    virtual bool quickRejectRect(float left, float top, float right, float bottom) const;
+    virtual bool quickRejectPath(const SkPath& path) const;
+    virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+    virtual bool clipPath(const SkPath* path, SkRegion::Op op);
+    virtual bool clipRegion(const SkRegion* region, SkRegion::Op op);
+
+    virtual void setDrawFilter(SkDrawFilter* drawFilter);
+
+    virtual void drawColor(int color, SkXfermode::Mode mode);
+    virtual void drawPaint(const SkPaint& paint);
+
+    virtual void drawPoint(float x, float y, const SkPaint& paint);
+    virtual void drawPoints(const float* points, int count, const SkPaint& paint);
+    virtual void drawLine(float startX, float startY, float stopX, float stopY,
+            const SkPaint& paint);
+    virtual void drawLines(const float* points, int count, const SkPaint& paint);
+    virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint);
+    virtual void drawRoundRect(float left, float top, float right, float bottom,
+            float rx, float ry, const SkPaint& paint);
+    virtual void drawCircle(float x, float y, float radius, const SkPaint& paint);
+    virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint);
+    virtual void drawArc(float left, float top, float right, float bottom,
+            float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint);
+    virtual void drawPath(const SkPath& path, const SkPaint& paint);
+    virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+            const float* verts, const float* tex, const int* colors,
+            const uint16_t* indices, int indexCount, const SkPaint& paint);
+
+    virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint);
+    virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint);
+    virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop,
+            float dstRight, float dstBottom, const SkPaint* paint);
+    virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+            const float* vertices, const int* colors, const SkPaint* paint);
+
+    virtual void drawText(const char* text, int start, int count, int contextCount,
+            float x, float y, int bidiFlags, const SkPaint& paint, TypefaceImpl* typeface);
+    virtual void drawPosText(const char* text, const float* positions, int count, int posCount,
+            const SkPaint& paint);
+    virtual void drawTextOnPath(const char* text, int count, const SkPath& path,
+            float hOffset, float vOffset, const SkPaint& paint);
+
+private:
+    struct SaveRec {
+        int                 saveCount;
+        SkCanvas::SaveFlags saveFlags;
+    };
+
+    void recordPartialSave(SkCanvas::SaveFlags flags);
+    void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount);
+    void applyClips(const SkTArray<SkClipStack::Element>& clips);
+
+    void drawPoints(const float* points, int count, const SkPaint& paint,
+                    SkCanvas::PointMode mode);
+    void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
+
+    SkAutoTUnref<SkCanvas> mCanvas;
+    SkAutoTDelete<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
+};
+
+// Construct an SkCanvas from the bitmap.
+static SkCanvas* createCanvas(SkBitmap* bitmap) {
+    if (bitmap) {
+        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);
+}
+
+Canvas* Canvas::create_canvas(SkBitmap* bitmap) {
+    return new SkiaCanvas(bitmap);
+}
+
+Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas) {
+    return new SkiaCanvas(skiaCanvas);
+}
+
+SkiaCanvas::SkiaCanvas(SkBitmap* bitmap) {
+    mCanvas.reset(createCanvas(bitmap));
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Replace Bitmap
+// ----------------------------------------------------------------------------
+
+class ClipCopier : public SkCanvas::ClipVisitor {
+public:
+    ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {}
+
+    virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) {
+        m_dstCanvas->clipRect(rect, op, antialias);
+    }
+    virtual void clipRRect(const SkRRect& rrect, SkRegion::Op op, bool antialias) {
+        m_dstCanvas->clipRRect(rrect, op, antialias);
+    }
+    virtual void clipPath(const SkPath& path, SkRegion::Op op, bool antialias) {
+        m_dstCanvas->clipPath(path, op, antialias);
+    }
+
+private:
+    SkCanvas* m_dstCanvas;
+};
+
+void SkiaCanvas::setBitmap(SkBitmap* bitmap, bool copyState) {
+    SkCanvas* newCanvas = createCanvas(bitmap);
+    SkASSERT(newCanvas);
+
+    if (copyState) {
+        // Copy the canvas matrix & clip state.
+        newCanvas->setMatrix(mCanvas->getTotalMatrix());
+        if (NULL != mCanvas->getDevice() && NULL != newCanvas->getDevice()) {
+            ClipCopier copier(newCanvas);
+            mCanvas->replayClips(&copier);
+        }
+    }
+
+    // unrefs the existing canvas
+    mCanvas.reset(newCanvas);
+
+    // clean up the old save stack
+    mSaveStack.reset(NULL);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations
+// ----------------------------------------------------------------------------
+
+bool SkiaCanvas::isOpaque() {
+    return mCanvas->getDevice()->accessBitmap(false).isOpaque();
+}
+
+int SkiaCanvas::width() {
+    return mCanvas->getBaseLayerSize().width();
+}
+
+int SkiaCanvas::height() {
+    return mCanvas->getBaseLayerSize().height();
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Save (layer)
+// ----------------------------------------------------------------------------
+
+int SkiaCanvas::getSaveCount() const {
+    return mCanvas->getSaveCount();
+}
+
+int SkiaCanvas::save(SkCanvas::SaveFlags flags) {
+    int count = mCanvas->save();
+    recordPartialSave(flags);
+    return count;
+}
+
+void SkiaCanvas::restore() {
+    const SaveRec* rec = (NULL == mSaveStack.get())
+            ? 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();
+}
+
+void SkiaCanvas::restoreToCount(int restoreCount) {
+    while (mCanvas->getSaveCount() > restoreCount) {
+        this->restore();
+    }
+}
+
+int SkiaCanvas::saveLayer(float left, float top, float right, float bottom,
+            const SkPaint* paint, SkCanvas::SaveFlags flags) {
+    SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+    int count = mCanvas->saveLayer(&bounds, paint, flags | SkCanvas::kMatrixClip_SaveFlag);
+    recordPartialSave(flags);
+    return count;
+}
+
+int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom,
+        int alpha, SkCanvas::SaveFlags flags) {
+    SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+    int count = mCanvas->saveLayerAlpha(&bounds, alpha, flags | SkCanvas::kMatrixClip_SaveFlag);
+    recordPartialSave(flags);
+    return count;
+}
+
+// ----------------------------------------------------------------------------
+// functions to emulate legacy SaveFlags (i.e. independent matrix/clip flags)
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::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.get()) {
+        mSaveStack.reset(SkNEW_ARGS(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 SkiaCanvas::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 SkiaCanvas::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);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Matrix
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::getMatrix(SkMatrix* outMatrix) const {
+    *outMatrix = mCanvas->getTotalMatrix();
+}
+
+void SkiaCanvas::setMatrix(const SkMatrix& matrix) {
+    mCanvas->setMatrix(matrix);
+}
+
+void SkiaCanvas::concat(const SkMatrix& matrix) {
+    mCanvas->concat(matrix);
+}
+
+void SkiaCanvas::rotate(float degrees) {
+    mCanvas->rotate(degrees);
+}
+
+void SkiaCanvas::scale(float sx, float sy) {
+    mCanvas->scale(sx, sy);
+}
+
+void SkiaCanvas::skew(float sx, float sy) {
+    mCanvas->skew(sx, sy);
+}
+
+void SkiaCanvas::translate(float dx, float dy) {
+    mCanvas->translate(dx, dy);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Clips
+// ----------------------------------------------------------------------------
+
+// This function is a mirror of SkCanvas::getClipBounds except that it does
+// not outset the edge of the clip to account for anti-aliasing. There is
+// a skia bug to investigate pushing this logic into back into skia.
+// (see https://code.google.com/p/skia/issues/detail?id=1303)
+bool SkiaCanvas::getClipBounds(SkRect* outRect) const {
+    SkIRect ibounds;
+    if (!mCanvas->getClipDeviceBounds(&ibounds)) {
+        return false;
+    }
+
+    SkMatrix inverse;
+    // if we can't invert the CTM, we can't return local clip bounds
+    if (!mCanvas->getTotalMatrix().invert(&inverse)) {
+        if (outRect) {
+            outRect->setEmpty();
+        }
+        return false;
+    }
+
+    if (NULL != outRect) {
+        SkRect r = SkRect::Make(ibounds);
+        inverse.mapRect(outRect, r);
+    }
+    return true;
+}
+
+bool SkiaCanvas::quickRejectRect(float left, float top, float right, float bottom) const {
+    SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+    return mCanvas->quickReject(bounds);
+}
+
+bool SkiaCanvas::quickRejectPath(const SkPath& path) const {
+    return mCanvas->quickReject(path);
+}
+
+bool SkiaCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
+    SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
+    mCanvas->clipRect(rect, op);
+    return mCanvas->isClipEmpty();
+}
+
+bool SkiaCanvas::clipPath(const SkPath* path, SkRegion::Op op) {
+    mCanvas->clipPath(*path, op);
+    return mCanvas->isClipEmpty();
+}
+
+bool SkiaCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) {
+    SkPath rgnPath;
+    if (region->getBoundaryPath(&rgnPath)) {
+        // The region is specified in device space.
+        SkMatrix savedMatrix = mCanvas->getTotalMatrix();
+        mCanvas->resetMatrix();
+        mCanvas->clipPath(rgnPath, op);
+        mCanvas->setMatrix(savedMatrix);
+    } else {
+        mCanvas->clipRect(SkRect::MakeEmpty(), op);
+    }
+    return mCanvas->isClipEmpty();
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Filters
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::setDrawFilter(SkDrawFilter* drawFilter) {
+    mCanvas->setDrawFilter(drawFilter);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::drawColor(int color, SkXfermode::Mode mode) {
+    mCanvas->drawColor(color, mode);
+}
+
+void SkiaCanvas::drawPaint(const SkPaint& paint) {
+    mCanvas->drawPaint(paint);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations: Geometry
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint,
+                            SkCanvas::PointMode mode) {
+    // convert the floats into SkPoints
+    count >>= 1;    // now it is the number of points
+    SkAutoSTMalloc<32, SkPoint> storage(count);
+    SkPoint* pts = storage.get();
+    for (int i = 0; i < count; i++) {
+        pts[i].set(points[0], points[1]);
+        points += 2;
+    }
+    mCanvas->drawPoints(mode, count, pts, paint);
+}
+
+
+void SkiaCanvas::drawPoint(float x, float y, const SkPaint& paint) {
+    mCanvas->drawPoint(x, y, paint);
+}
+
+void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
+    this->drawPoints(points, count, paint, SkCanvas::kPoints_PointMode);
+}
+
+void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY,
+                          const SkPaint& paint) {
+    mCanvas->drawLine(startX, startY, stopX, stopY, paint);
+}
+
+void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
+    this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
+}
+
+void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
+        const SkPaint& paint) {
+    mCanvas->drawRectCoords(left, top, right, bottom, paint);
+
+}
+
+void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom,
+        float rx, float ry, const SkPaint& paint) {
+    SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
+    mCanvas->drawRoundRect(rect, rx, ry, paint);
+}
+
+void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
+    mCanvas->drawCircle(x, y, radius, paint);
+}
+
+void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
+    SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
+    mCanvas->drawOval(oval, paint);
+}
+
+void SkiaCanvas::drawArc(float left, float top, float right, float bottom,
+        float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
+    SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
+    mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+}
+
+void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+    mCanvas->drawPath(path, paint);
+}
+
+void SkiaCanvas::drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+                              const float* verts, const float* texs, const int* colors,
+                              const uint16_t* indices, int indexCount, const SkPaint& paint) {
+#ifndef SK_SCALAR_IS_FLOAT
+    SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
+#endif
+    const int ptCount = vertexCount >> 1;
+    mCanvas->drawVertices(vertexMode, ptCount, (SkPoint*)verts, (SkPoint*)texs,
+                          (SkColor*)colors, NULL, indices, indexCount, paint);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations: Bitmaps
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
+    mCanvas->drawBitmap(bitmap, left, top, paint);
+}
+
+void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) {
+    mCanvas->drawBitmapMatrix(bitmap, matrix, paint);
+}
+
+void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+                            float srcRight, float srcBottom, float dstLeft, float dstTop,
+                            float dstRight, float dstBottom, const SkPaint* paint) {
+    SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
+    SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+    mCanvas->drawBitmapRectToRect(bitmap, &srcRect, dstRect, paint);
+}
+
+void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+        const float* vertices, const int* colors, const SkPaint* paint) {
+
+    const int ptCount = (meshWidth + 1) * (meshHeight + 1);
+    const int indexCount = meshWidth * meshHeight * 6;
+
+    /*  Our temp storage holds 2 or 3 arrays.
+        texture points [ptCount * sizeof(SkPoint)]
+        optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
+            copy to convert from float to fixed
+        indices [ptCount * sizeof(uint16_t)]
+    */
+    ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
+    storageSize += indexCount * sizeof(uint16_t);  // indices[]
+
+
+#ifndef SK_SCALAR_IS_FLOAT
+    SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
+#endif
+    SkAutoMalloc storage(storageSize);
+    SkPoint* texs = (SkPoint*)storage.get();
+    uint16_t* indices = (uint16_t*)(texs + ptCount);
+
+    // cons up texture coordinates and indices
+    {
+        const SkScalar w = SkIntToScalar(bitmap.width());
+        const SkScalar h = SkIntToScalar(bitmap.height());
+        const SkScalar dx = w / meshWidth;
+        const SkScalar dy = h / meshHeight;
+
+        SkPoint* texsPtr = texs;
+        SkScalar y = 0;
+        for (int i = 0; i <= meshHeight; i++) {
+            if (i == meshHeight) {
+                y = h;  // to ensure numerically we hit h exactly
+            }
+            SkScalar x = 0;
+            for (int j = 0; j < meshWidth; j++) {
+                texsPtr->set(x, y);
+                texsPtr += 1;
+                x += dx;
+            }
+            texsPtr->set(w, y);
+            texsPtr += 1;
+            y += dy;
+        }
+        SkASSERT(texsPtr - texs == ptCount);
+    }
+
+    // cons up indices
+    {
+        uint16_t* indexPtr = indices;
+        int index = 0;
+        for (int i = 0; i < meshHeight; i++) {
+            for (int j = 0; j < meshWidth; j++) {
+                // lower-left triangle
+                *indexPtr++ = index;
+                *indexPtr++ = index + meshWidth + 1;
+                *indexPtr++ = index + meshWidth + 2;
+                // upper-right triangle
+                *indexPtr++ = index;
+                *indexPtr++ = index + meshWidth + 2;
+                *indexPtr++ = index + 1;
+                // bump to the next cell
+                index += 1;
+            }
+            // bump to the next row
+            index += 1;
+        }
+        SkASSERT(indexPtr - indices == indexCount);
+        SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
+    }
+
+    // double-check that we have legal indices
+#ifdef SK_DEBUG
+    {
+        for (int i = 0; i < indexCount; i++) {
+            SkASSERT((unsigned)indices[i] < (unsigned)ptCount);
+        }
+    }
+#endif
+
+    // cons-up a shader for the bitmap
+    SkPaint tmpPaint;
+    if (paint) {
+        tmpPaint = *paint;
+    }
+    SkShader* shader = SkShader::CreateBitmapShader(bitmap,
+                                                    SkShader::kClamp_TileMode,
+                                                    SkShader::kClamp_TileMode);
+    SkSafeUnref(tmpPaint.setShader(shader));
+
+    mCanvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, (SkPoint*)vertices,
+                         texs, (const SkColor*)colors, NULL, indices,
+                         indexCount, tmpPaint);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations: Text
+// ----------------------------------------------------------------------------
+
+class DrawTextFunctor {
+public:
+    DrawTextFunctor(const Layout& layout, SkCanvas* canvas, float x, float y, SkPaint* paint,
+                uint16_t* glyphs, SkPoint* pos)
+            : layout(layout), canvas(canvas), x(x), y(y), paint(paint), glyphs(glyphs),
+                pos(pos) { }
+
+    void operator()(size_t start, size_t end) {
+        for (size_t i = start; i < end; i++) {
+            glyphs[i] = layout.getGlyphId(i);
+            pos[i].fX = x + layout.getX(i);
+            pos[i].fY = y + layout.getY(i);
+        }
+        canvas->drawPosText(glyphs + start, (end - start) << 1, pos + start, *paint);
+    }
+private:
+    const Layout& layout;
+    SkCanvas* canvas;
+    float x;
+    float y;
+    SkPaint* paint;
+    uint16_t* glyphs;
+    SkPoint* pos;
+};
+
+void SkiaCanvas::drawText(const char* text, int start, int count, int contextCount,
+        float x, float y, int bidiFlags, const SkPaint& paint, TypefaceImpl* typeface) {
+    Layout layout;
+    std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
+    layout.doLayout((uint16_t*)text, start, count, contextCount, css);
+
+    size_t nGlyphs = layout.nGlyphs();
+    uint16_t* glyphs = new uint16_t[nGlyphs];
+    SkPoint* pos = new SkPoint[nGlyphs];
+
+    SkPaint paintCopy(paint);
+    x += MinikinUtils::xOffsetForTextAlign(&paintCopy, layout);
+    paintCopy.setTextAlign(SkPaint::kLeft_Align);
+    paintCopy.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+    DrawTextFunctor f(layout, mCanvas, x, y, &paintCopy, glyphs, pos);
+    MinikinUtils::forFontRun(layout, &paintCopy, f);
+    drawTextDecorations(x, y, layout.getAdvance(), paintCopy);
+
+    delete[] glyphs;
+    delete[] pos;
+}
+
+// Same values used by Skia
+#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
+#define kStdUnderline_Offset    (1.0f / 9.0f)
+#define kStdUnderline_Thickness (1.0f / 18.0f)
+
+void SkiaCanvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+    uint32_t flags;
+    SkDrawFilter* drawFilter = mCanvas->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;
+            mCanvas->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;
+            mCanvas->drawRectCoords(left, top, right, bottom, paint);
+        }
+    }
+}
+
+void SkiaCanvas::drawPosText(const char* text, const float* positions, int count, int posCount,
+        const SkPaint& paint) {
+    SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
+    int indx;
+    for (indx = 0; indx < posCount; indx++) {
+        posPtr[indx].fX = positions[indx << 1];
+        posPtr[indx].fY = positions[(indx << 1) + 1];
+    }
+
+    SkPaint paintCopy(paint);
+    paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+    mCanvas->drawPosText(text, count, posPtr, paintCopy);
+
+    delete[] posPtr;
+}
+
+void SkiaCanvas::drawTextOnPath(const char* text, int count, const SkPath& path,
+        float hOffset, float vOffset, const SkPaint& paint) {
+    mCanvas->drawTextOnPathHV(text, count, path, hOffset, vOffset, paint);
+}
+
+} // namespace android
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index 3812c27..9436a47 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -19,9 +19,9 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <vector>
 
+#include "Canvas.h"
 #include "CreateJavaOutputStreamAdaptor.h"
 
-#include "SkCanvas.h"
 #include "SkDocument.h"
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
@@ -132,8 +132,9 @@
         jint pageWidth, jint pageHeight,
         jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) {
     PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
-    return reinterpret_cast<jlong>(document->startPage(pageWidth, pageHeight,
-            contentLeft, contentTop, contentRight, contentBottom));
+    SkCanvas* canvas = document->startPage(pageWidth, pageHeight,
+            contentLeft, contentTop, contentRight, contentBottom);
+    return reinterpret_cast<jlong>(Canvas::create_canvas(canvas));
 }
 
 static void nativeFinishPage(JNIEnv* env, jobject thiz, jlong documentPtr) {
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
new file mode 100644
index 0000000..fd96a90
--- /dev/null
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "Canvas.h"
+#include "SkGraphics.h"
+#include "SkPorterDuff.h"
+#include "TypefaceImpl.h"
+
+#include <minikin/Layout.h>
+#include "MinikinSkia.h"
+#include "MinikinUtils.h"
+
+namespace android {
+
+namespace CanvasJNI {
+
+static Canvas* get_canvas(jlong canvasHandle) {
+    return reinterpret_cast<Canvas*>(canvasHandle);
+}
+
+static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) {
+    delete get_canvas(canvasHandle);
+}
+
+// Native wrapper constructor used by Canvas(Bitmap)
+static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) {
+    SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+    return reinterpret_cast<jlong>(Canvas::create_canvas(bitmap));
+}
+
+// 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) {
+    SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+    get_canvas(canvasHandle)->setBitmap(bitmap, copyState);
+}
+
+static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) {
+    return get_canvas(canvasHandle)->isOpaque() ? JNI_TRUE : JNI_FALSE;
+}
+
+static jint getWidth(JNIEnv*, jobject, jlong canvasHandle) {
+    return static_cast<jint>(get_canvas(canvasHandle)->width());
+}
+
+static jint getHeight(JNIEnv*, jobject, jlong canvasHandle) {
+    return static_cast<jint>(get_canvas(canvasHandle)->height());
+}
+
+static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) {
+    return static_cast<jint>(get_canvas(canvasHandle)->getSaveCount());
+}
+
+static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
+    SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+    return static_cast<jint>(get_canvas(canvasHandle)->save(flags));
+}
+
+static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+                      jfloat r, jfloat b, jlong paintHandle, jint flagsHandle) {
+    SkPaint* paint  = reinterpret_cast<SkPaint*>(paintHandle);
+    SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+    return static_cast<jint>(get_canvas(canvasHandle)->saveLayer(l, t, r, b, paint, flags));
+}
+
+static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+                           jfloat r, jfloat b, jint alpha, jint flagsHandle) {
+    SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+    return static_cast<jint>(get_canvas(canvasHandle)->saveLayerAlpha(l, t, r, b, alpha, flags));
+}
+
+static void restore(JNIEnv* env, jobject, jlong canvasHandle) {
+    Canvas* canvas = get_canvas(canvasHandle);
+    if (canvas->getSaveCount() <= 1) {  // cannot restore anymore
+        doThrowISE(env, "Underflow in restore");
+        return;
+    }
+    canvas->restore();
+}
+
+static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle, jint restoreCount) {
+    Canvas* canvas = get_canvas(canvasHandle);
+    if (restoreCount < 1 || restoreCount > canvas->getSaveCount()) {
+        doThrowIAE(env, "Underflow in restoreToCount");
+        return;
+    }
+    canvas->restoreToCount(restoreCount);
+}
+
+static void getCTM(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+    SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+    get_canvas(canvasHandle)->getMatrix(matrix);
+}
+
+static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+    const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+    get_canvas(canvasHandle)->setMatrix(matrix ? *matrix : SkMatrix::I());
+}
+
+static void concat(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+    const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+    get_canvas(canvasHandle)->concat(*matrix);
+}
+
+static void rotate(JNIEnv*, jobject, jlong canvasHandle, jfloat degrees) {
+    get_canvas(canvasHandle)->rotate(degrees);
+}
+
+static void scale(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+    get_canvas(canvasHandle)->scale(sx, sy);
+}
+
+static void skew(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+    get_canvas(canvasHandle)->skew(sx, sy);
+}
+
+static void translate(JNIEnv*, jobject, jlong canvasHandle, jfloat dx, jfloat dy) {
+    get_canvas(canvasHandle)->translate(dx, dy);
+}
+
+static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds) {
+    SkRect   r;
+    SkIRect ir;
+    bool result = get_canvas(canvasHandle)->getClipBounds(&r);
+
+    if (!result) {
+        r.setEmpty();
+    }
+    r.round(&ir);
+
+    (void)GraphicsJNI::irect_to_jrect(ir, env, bounds);
+    return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean quickRejectRect(JNIEnv* env, jobject, jlong canvasHandle,
+                                jfloat left, jfloat top, jfloat right, jfloat bottom) {
+    bool result = get_canvas(canvasHandle)->quickRejectRect(left, top, right, bottom);
+    return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean quickRejectPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle) {
+    SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+    bool result = get_canvas(canvasHandle)->quickRejectPath(*path);
+    return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean clipRect(JNIEnv*, jobject, jlong canvasHandle, jfloat l, jfloat t,
+                         jfloat r, jfloat b, jint opHandle) {
+    SkRegion::Op op = static_cast<SkRegion::Op>(opHandle);
+    bool emptyClip = get_canvas(canvasHandle)->clipRect(l, t, r, b, op);
+    return emptyClip ? JNI_FALSE : JNI_TRUE;
+}
+
+static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
+                         jint opHandle) {
+    SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+    SkRegion::Op op = static_cast<SkRegion::Op>(opHandle);
+    bool emptyClip = get_canvas(canvasHandle)->clipPath(path, op);
+    return emptyClip ? JNI_FALSE : JNI_TRUE;
+}
+
+static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle, jlong deviceRgnHandle,
+                           jint opHandle) {
+    SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle);
+    SkRegion::Op op = static_cast<SkRegion::Op>(opHandle);
+    bool emptyClip = get_canvas(canvasHandle)->clipRegion(deviceRgn, op);
+    return emptyClip ? JNI_FALSE : JNI_TRUE;
+}
+
+static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) {
+     SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
+     get_canvas(canvasHandle)->drawColor(color, SkPorterDuff::ToXfermodeMode(mode));
+}
+
+static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle, jlong paintHandle) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawPaint(*paint);
+}
+
+static void drawPoint(JNIEnv*, jobject, jlong canvasHandle, jfloat x, jfloat y,
+                      jlong paintHandle) {
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawPoint(x, y, *paint);
+}
+
+static void drawPoints(JNIEnv* env, jobject, jlong canvasHandle, jfloatArray jptsArray,
+                       jint offset, jint count, jlong paintHandle) {
+    NPE_CHECK_RETURN_VOID(env, jptsArray);
+    AutoJavaFloatArray autoPts(env, jptsArray);
+    float* floats = autoPts.ptr();
+    const int length = autoPts.length();
+
+    if ((offset | count) < 0 || offset + count > length) {
+        doThrowAIOOBE(env);
+        return;
+    }
+
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawPoints(floats + offset, count, *paint);
+}
+
+static void drawLine(JNIEnv* env, jobject, jlong canvasHandle, jfloat startX, jfloat startY,
+                     jfloat stopX, jfloat stopY, jlong paintHandle) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawLine(startX, startY, stopX, stopY, *paint);
+}
+
+static void drawLines(JNIEnv* env, jobject, jlong canvasHandle, jfloatArray jptsArray,
+                      jint offset, jint count, jlong paintHandle) {
+    NPE_CHECK_RETURN_VOID(env, jptsArray);
+    AutoJavaFloatArray autoPts(env, jptsArray);
+    float* floats = autoPts.ptr();
+    const int length = autoPts.length();
+
+    if ((offset | count) < 0 || offset + count > length) {
+        doThrowAIOOBE(env);
+        return;
+    }
+
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawLines(floats + offset, count, *paint);
+}
+
+static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+                     jfloat right, jfloat bottom, jlong paintHandle) {
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint);
+}
+
+static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+                          jfloat right, jfloat bottom, jfloat rx, jfloat ry, jlong paintHandle) {
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawRoundRect(left, top, right, bottom, rx, ry, *paint);
+}
+
+static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx, jfloat cy,
+                       jfloat radius, jlong paintHandle) {
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawCircle(cx, cy, radius, *paint);
+}
+
+static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+                     jfloat right, jfloat bottom, jlong paintHandle) {
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawOval(left, top, right, bottom, *paint);
+}
+
+static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+                    jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle,
+                    jboolean useCenter, jlong paintHandle) {
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawArc(left, top, right, bottom, startAngle, sweepAngle,
+                                       useCenter, *paint);
+}
+
+static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
+                     jlong paintHandle) {
+    const SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawPath(*path, *paint);
+}
+
+static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
+                         jint modeHandle, jint vertexCount,
+                         jfloatArray jverts, jint vertIndex,
+                         jfloatArray jtexs, jint texIndex,
+                         jintArray jcolors, jint colorIndex,
+                         jshortArray jindices, jint indexIndex,
+                         jint indexCount, jlong paintHandle) {
+    AutoJavaFloatArray  vertA(env, jverts, vertIndex + vertexCount);
+    AutoJavaFloatArray  texA(env, jtexs, texIndex + vertexCount);
+    AutoJavaIntArray    colorA(env, jcolors, colorIndex + vertexCount);
+    AutoJavaShortArray  indexA(env, jindices, indexIndex + indexCount);
+
+    const float* verts = vertA.ptr() + vertIndex;
+    const float* texs = texA.ptr() + vertIndex;
+    const int* colors = NULL;
+    const uint16_t* indices = NULL;
+
+    if (jcolors != NULL) {
+        colors = colorA.ptr() + colorIndex;
+    }
+    if (jindices != NULL) {
+        indices = (const uint16_t*)(indexA.ptr() + indexIndex);
+    }
+
+    SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle);
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawVertices(mode, vertexCount, verts, texs, colors,
+                                           indices, indexCount, *paint);
+}
+
+static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong bitmapHandle,
+                       jfloat left, jfloat top, jlong paintHandle, jint canvasDensity,
+                       jint screenDensity, jint bitmapDensity) {
+    Canvas* canvas = get_canvas(canvasHandle);
+    const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+
+    if (canvasDensity == bitmapDensity || canvasDensity == 0 || bitmapDensity == 0) {
+        if (screenDensity != 0 && screenDensity != bitmapDensity) {
+            SkPaint filteredPaint;
+            if (paint) {
+                filteredPaint = *paint;
+            }
+            filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
+            canvas->drawBitmap(*bitmap, left, top, &filteredPaint);
+        } else {
+            canvas->drawBitmap(*bitmap, left, top, paint);
+        }
+    } else {
+        canvas->save(SkCanvas::kMatrixClip_SaveFlag);
+        SkScalar scale = canvasDensity / (float)bitmapDensity;
+        canvas->translate(left, top);
+        canvas->scale(scale, scale);
+
+        SkPaint filteredPaint;
+        if (paint) {
+            filteredPaint = *paint;
+        }
+        filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
+
+        canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
+        canvas->restore();
+    }
+}
+
+static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+                             jlong matrixHandle, jlong paintHandle) {
+    const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+    const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawBitmap(*bitmap, *matrix, paint);
+}
+
+static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+                           float srcLeft, float srcTop, float srcRight, float srcBottom,
+                           float dstLeft, float dstTop, float dstRight, float dstBottom,
+                           jlong paintHandle, jint screenDensity, jint bitmapDensity) {
+    Canvas* canvas = get_canvas(canvasHandle);
+    const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+
+    if (screenDensity != 0 && screenDensity != bitmapDensity) {
+        SkPaint filteredPaint;
+        if (paint) {
+            filteredPaint = *paint;
+        }
+        filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
+        canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom,
+                           dstLeft, dstTop, dstRight, dstBottom, &filteredPaint);
+    } else {
+        canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom,
+                           dstLeft, dstTop, dstRight, dstBottom, paint);
+    }
+}
+
+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) {
+    // Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will
+    // correct the alphaType to kOpaque_SkAlphaType.
+    SkImageInfo info = SkImageInfo::Make(width, height,
+                           hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+                           kPremul_SkAlphaType);
+    SkBitmap bitmap;
+    if (!bitmap.allocPixels(info)) {
+        return;
+    }
+
+    if (!GraphicsJNI::SetPixels(env, jcolors, offset, stride, 0, 0, width, height, bitmap)) {
+        return;
+    }
+
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawBitmap(bitmap, x, y, paint);
+}
+
+static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+                           jint meshWidth, jint meshHeight, jfloatArray jverts,
+                           jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) {
+    const int ptCount = (meshWidth + 1) * (meshHeight + 1);
+    AutoJavaFloatArray vertA(env, jverts, vertIndex + (ptCount << 1));
+    AutoJavaIntArray colorA(env, jcolors, colorIndex + ptCount);
+
+    const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+    const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    get_canvas(canvasHandle)->drawBitmapMesh(*bitmap, meshWidth, meshHeight,
+                                             vertA.ptr(), colorA.ptr(), paint);
+}
+
+static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
+                          jint index, jint count, jfloat x, jfloat y, jint bidiFlags,
+                          jlong paintHandle, jlong typefaceHandle) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+    jchar* jchars = env->GetCharArrayElements(text, NULL);
+    const char* textArray = reinterpret_cast<const char*>(jchars) + index;
+    get_canvas(canvasHandle)->drawText(textArray, 0, count, count, x, y, bidiFlags, *paint, typeface);
+    env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
+}
+
+static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring text,
+                           jint start, jint end, jfloat x, jfloat y, jint bidiFlags,
+                           jlong paintHandle, jlong typefaceHandle) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+    const int count = end - start;
+    const jchar* jchars = env->GetStringChars(text, NULL);
+    const char* textArray = reinterpret_cast<const char*>(jchars) + start;
+    get_canvas(canvasHandle)->drawText(textArray, 0, count, count, x, y, bidiFlags, *paint, typeface);
+    env->ReleaseStringChars(text, jchars);
+}
+
+static void drawTextRunChars(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) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
+    const int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
+    jchar* jchars = env->GetCharArrayElements(text, NULL);
+    const char* textArray = reinterpret_cast<const char*>(jchars) + contextIndex;
+    get_canvas(canvasHandle)->drawText(textArray, index - contextIndex, count, contextCount,
+                                       x, y, bidiFlags, *paint, typeface);
+    env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
+}
+
+static void drawTextRunString(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) {
+    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* jchars = env->GetStringChars(text, NULL);
+    const char* textArray = reinterpret_cast<const char*>(jchars) + contextStart;
+    get_canvas(canvasHandle)->drawText(textArray, start - contextStart, count, contextCount,
+                                       x, y, bidiFlags, *paint, typeface);
+    env->ReleaseStringChars(text, jchars);
+}
+
+static void drawPosTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
+                             jint index, jint count, jfloatArray pos, jlong paintHandle) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    jchar* jchars = text ? env->GetCharArrayElements(text, NULL) : NULL;
+    float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
+    int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
+
+    const char* textArray = reinterpret_cast<const char*>(jchars) + index;
+    get_canvas(canvasHandle)->drawPosText(textArray, posArray, count << 1, posCount, *paint);
+
+    if (text) {
+        env->ReleaseCharArrayElements(text, jchars, 0);
+    }
+    if (pos) {
+        env->ReleaseFloatArrayElements(pos, posArray, 0);
+    }
+}
+
+
+static void drawPosTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring text,
+                              jfloatArray pos, jlong paintHandle) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    const jchar* jchars = text ? env->GetStringChars(text, NULL) : NULL;
+    int byteLength = text ? env->GetStringLength(text) : 0;
+    float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
+    int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
+
+    const char* textArray = reinterpret_cast<const char*>(jchars);
+    get_canvas(canvasHandle)->drawPosText(textArray , posArray, byteLength << 1, posCount, *paint);
+
+    if (text) {
+        env->ReleaseStringChars(text, jchars);
+    }
+    if (pos) {
+        env->ReleaseFloatArrayElements(pos, posArray, 0);
+    }
+}
+
+class DrawTextOnPathFunctor {
+public:
+    DrawTextOnPathFunctor(const Layout& layout, Canvas* canvas, float hOffset,
+                float vOffset, const SkPaint& paint, const 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->drawTextOnPath((const char*) glyphs, 1, path, x, y, paint);
+        }
+    }
+private:
+    const Layout& layout;
+    Canvas* canvas;
+    float hOffset;
+    float vOffset;
+    const SkPaint& paint;
+    const SkPath& path;
+};
+
+static void drawTextOnPath(Canvas* canvas, const char* text, int count, int bidiFlags,
+                           const SkPath& path, float hOffset, float vOffset,
+                           const SkPaint& paint, TypefaceImpl* typeface) {
+    SkPaint paintCopy(paint);
+    Layout layout;
+    std::string css = MinikinUtils::setLayoutProperties(&layout, &paintCopy, bidiFlags, typeface);
+    layout.doLayout((uint16_t*)text, 0, count, count, css);
+    hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, 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.
+    paintCopy.setTextAlign(SkPaint::kLeft_Align);
+
+    DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paintCopy, path);
+    MinikinUtils::forFontRun(layout, &paintCopy, f);
+}
+
+static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
+                                jint index, jint count, jlong pathHandle, jfloat hOffset,
+                                jfloat vOffset, jint bidiFlags, jlong paintHandle,
+                                jlong typefaceHandle) {
+    SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
+    jchar* jchars = env->GetCharArrayElements(text, NULL);
+    const char* textArray = reinterpret_cast<const char*>(jchars);
+
+    drawTextOnPath(get_canvas(canvasHandle), textArray + index, count, bidiFlags, *path,
+                   hOffset, vOffset, *paint, typeface);
+
+    env->ReleaseCharArrayElements(text, jchars, 0);
+}
+
+static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstring text,
+                                 jlong pathHandle, jfloat hOffset, jfloat vOffset,
+                                 jint bidiFlags, jlong paintHandle, jlong typefaceHandle) {
+    SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
+    const jchar* jchars = env->GetStringChars(text, NULL);
+    const char* textArray = reinterpret_cast<const char*>(jchars);
+    int count = env->GetStringLength(text);
+
+    drawTextOnPath(get_canvas(canvasHandle), textArray, count, bidiFlags, *path,
+                   hOffset, vOffset, *paint, typeface);
+
+    env->ReleaseStringChars(text, jchars);
+}
+
+static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle, jlong filterHandle) {
+    get_canvas(canvasHandle)->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
+}
+
+static void freeCaches(JNIEnv* env, jobject) {
+    SkGraphics::PurgeFontCache();
+}
+
+static void freeTextLayoutCaches(JNIEnv* env, jobject) {
+    Layout::purgeCaches();
+}
+
+}; // namespace CanvasJNI
+
+static JNINativeMethod gMethods[] = {
+    {"finalizer", "(J)V", (void*) CanvasJNI::finalizer},
+    {"initRaster", "(J)J", (void*) CanvasJNI::initRaster},
+    {"native_setBitmap", "(JJZ)V", (void*) CanvasJNI::setBitmap},
+    {"native_isOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
+    {"native_getWidth","(J)I", (void*) CanvasJNI::getWidth},
+    {"native_getHeight","(J)I", (void*) CanvasJNI::getHeight},
+    {"native_save","(JI)I", (void*) CanvasJNI::save},
+    {"native_saveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
+    {"native_saveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
+    {"native_getSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
+    {"native_restore","(J)V", (void*) CanvasJNI::restore},
+    {"native_restoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
+    {"native_getCTM", "(JJ)V", (void*)CanvasJNI::getCTM},
+    {"native_setMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
+    {"native_concat","(JJ)V", (void*) CanvasJNI::concat},
+    {"native_rotate","(JF)V", (void*) CanvasJNI::rotate},
+    {"native_scale","(JFF)V", (void*) CanvasJNI::scale},
+    {"native_skew","(JFF)V", (void*) CanvasJNI::skew},
+    {"native_translate","(JFF)V", (void*) CanvasJNI::translate},
+    {"native_getClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+    {"native_quickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
+    {"native_quickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+    {"native_clipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
+    {"native_clipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
+    {"native_clipRegion","(JJI)Z", (void*) CanvasJNI::clipRegion},
+    {"native_drawColor","(JII)V", (void*) CanvasJNI::drawColor},
+    {"native_drawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
+    {"native_drawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
+    {"native_drawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
+    {"native_drawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
+    {"native_drawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
+    {"native_drawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
+    {"native_drawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
+    {"native_drawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
+    {"native_drawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
+    {"native_drawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
+    {"native_drawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
+    {"nativeDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
+    {"native_drawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
+    {"nativeDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+    {"native_drawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
+    {"native_drawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+    {"nativeDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+    {"native_drawText","(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars},
+    {"native_drawText","(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString},
+    {"native_drawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
+    {"native_drawTextRun","(JLjava/lang/String;IIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunString},
+    {"native_drawTextOnPath","(J[CIIJFFIJJ)V", (void*) CanvasJNI::drawTextOnPathChars},
+    {"native_drawTextOnPath","(JLjava/lang/String;JFFIJJ)V", (void*) CanvasJNI::drawTextOnPathString},
+    {"nativeSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setDrawFilter},
+    {"freeCaches", "()V", (void*) CanvasJNI::freeCaches},
+    {"freeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches}
+};
+
+int register_android_graphics_Canvas(JNIEnv* env) {
+    return AndroidRuntime::registerNativeMethods(env, "android/graphics/Canvas", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_graphics_Picture.cpp b/core/jni/android_graphics_Picture.cpp
index f827907..eb8f6dd 100644
--- a/core/jni/android_graphics_Picture.cpp
+++ b/core/jni/android_graphics_Picture.cpp
@@ -51,7 +51,7 @@
 
 static void android_graphics_Picture_draw(JNIEnv* env, jobject, jlong canvasHandle,
                                           jlong pictureHandle) {
-    SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+    Canvas* canvas = reinterpret_cast<Canvas*>(canvasHandle);
     Picture* picture = reinterpret_cast<Picture*>(pictureHandle);
     SkASSERT(canvas);
     SkASSERT(picture);
@@ -84,12 +84,7 @@
 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();
+    Canvas* canvas = pict->beginRecording(w, h);
     return reinterpret_cast<jlong>(canvas);
 }
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 64366e5..1f7acec 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -39,6 +39,7 @@
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
 #include <cutils/sched_policy.h>
+#include <private/android_filesystem_config.h>
 #include <utils/String8.h>
 #include <selinux/android.h>
 #include <processgroup/processgroup.h>
@@ -536,8 +537,15 @@
         jint debug_flags, jobjectArray rlimits,
         jint mount_external, jstring se_info, jstring se_name,
         jintArray fdsToClose) {
+    // Grant CAP_WAKE_ALARM to the Bluetooth process.
+    jlong capabilities = 0;
+    if (uid == AID_BLUETOOTH) {
+        capabilities |= (1LL << CAP_WAKE_ALARM);
+    }
+
     return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags,
-            rlimits, 0, 0, mount_external, se_info, se_name, false, fdsToClose);
+            rlimits, capabilities, capabilities, mount_external, se_info,
+            se_name, false, fdsToClose);
 }
 
 static jint com_android_internal_os_Zygote_nativeForkSystemServer(
diff --git a/core/res/res/anim/launch_task_behind_background.xml b/core/res/res/anim/launch_task_behind_background.xml
new file mode 100644
index 0000000..358511f
--- /dev/null
+++ b/core/res/res/anim/launch_task_behind_background.xml
@@ -0,0 +1,34 @@
+<?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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
+
+    <translate android:fromYDelta="110%" android:toYDelta="66%"
+               android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+               android:interpolator="@interpolator/decelerate_quint"
+               android:startOffset="50"
+               android:duration="300" />
+
+    <translate android:fromYDelta="0%" android:toYDelta="167%"
+               android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+               android:interpolator="@interpolator/accelerate_quint"
+               android:startOffset="433"
+               android:duration="300" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/launch_task_behind_source.xml b/core/res/res/anim/launch_task_behind_source.xml
new file mode 100644
index 0000000..426ee5d
--- /dev/null
+++ b/core/res/res/anim/launch_task_behind_source.xml
@@ -0,0 +1,59 @@
+<?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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
+
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.6"
+        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/accelerate_cubic"
+        android:duration="133"/>
+
+    <translate android:fromYDelta="0" android:toYDelta="10%"
+        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/accelerate_cubic"
+        android:duration="350"/>
+
+    <scale android:fromXScale="1.0" android:toXScale="0.9"
+        android:fromYScale="1.0" android:toYScale="0.9"
+        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+        android:pivotX="50%p" android:pivotY="50%p"
+        android:interpolator="@interpolator/fast_out_slow_in"
+        android:duration="350" />
+
+    <alpha android:fromAlpha="1.0" android:toAlpha="1.6"
+        android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+        android:interpolator="@interpolator/decelerate_cubic"
+        android:startOffset="433"
+        android:duration="133"/>
+
+    <translate android:fromYDelta="0%" android:toYDelta="-10%"
+        android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+        android:interpolator="@interpolator/decelerate_cubic"
+        android:startOffset="433"
+        android:duration="350"/>
+
+    <scale android:fromXScale="1.0" android:toXScale="1.1"
+        android:fromYScale="1.0" android:toYScale="1.1"
+        android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+        android:pivotX="50%p" android:pivotY="50%p"
+        android:interpolator="@interpolator/decelerate_cubic"
+        android:startOffset="433"
+        android:duration="350" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_holo_dark.xml b/core/res/res/color/date_picker_calendar_holo_dark.xml
new file mode 100644
index 0000000..d29486f
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_holo_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_enabled="true" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_holo_dark"/>
+    <item android:state_enabled="true" android:state_selected="true"
+          android:color="@color/holo_blue_light"/>
+    <item android:state_enabled="false"
+          android:color="@color/datepicker_default_disabled_text_color_holo_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_holo_light.xml b/core/res/res/color/date_picker_calendar_holo_light.xml
new file mode 100644
index 0000000..776f39b
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_holo_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_enabled="true" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_holo_light"/>
+    <item android:state_enabled="true" android:state_selected="true"
+          android:color="@color/holo_blue_light"/>
+    <item android:state_enabled="false"
+          android:color="@color/datepicker_default_disabled_text_color_holo_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_material_dark.xml b/core/res/res/color/date_picker_calendar_material_dark.xml
new file mode 100644
index 0000000..86a0673
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_material_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_enabled="true" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_material_dark"/>
+    <item android:state_enabled="true" android:state_selected="true"
+          android:color="@color/holo_blue_light"/>
+    <item android:state_enabled="false"
+          android:color="@color/datepicker_default_disabled_text_color_material_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_material_light.xml b/core/res/res/color/date_picker_calendar_material_light.xml
new file mode 100644
index 0000000..015eed7
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_material_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_enabled="true" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_material_light"/>
+    <item android:state_enabled="true" android:state_selected="true"
+          android:color="@color/holo_blue_light"/>
+    <item android:state_enabled="false"
+          android:color="@color/datepicker_default_disabled_text_color_material_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_holo_dark.xml b/core/res/res/color/date_picker_selector_holo_dark.xml
new file mode 100644
index 0000000..9e5a5bd
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_holo_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_holo_dark"/>
+    <item android:state_pressed="false" android:state_selected="true"
+          android:color="@color/datepicker_default_selected_text_color_holo_dark"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_holo_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_holo_light.xml b/core/res/res/color/date_picker_selector_holo_light.xml
new file mode 100644
index 0000000..bf8667c
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_holo_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_holo_light"/>
+    <item android:state_pressed="false" android:state_selected="true"
+          android:color="@color/datepicker_default_selected_text_color_holo_light"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_holo_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_material_dark.xml b/core/res/res/color/date_picker_selector_material_dark.xml
new file mode 100644
index 0000000..e407387
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_material_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_material_dark"/>
+    <item android:state_pressed="false" android:state_selected="true"
+          android:color="@color/datepicker_default_selected_text_color_material_dark"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_material_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_material_light.xml b/core/res/res/color/date_picker_selector_material_light.xml
new file mode 100644
index 0000000..b4c6a47
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_material_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_material_light"/>
+    <item android:state_pressed="false" android:state_selected="true"
+          android:color="@color/datepicker_default_selected_text_color_material_light"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_material_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/color/date_picker_year_selector_holo_dark.xml
similarity index 64%
copy from core/res/res/drawable/lock_task_notify_bg.xml
copy to core/res/res/color/date_picker_year_selector_holo_dark.xml
index 3a8fab5..ce519b2 100644
--- a/core/res/res/drawable/lock_task_notify_bg.xml
+++ b/core/res/res/color/date_picker_year_selector_holo_dark.xml
@@ -13,10 +13,11 @@
      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" >
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <solid android:color="@android:color/black" />
-    <corners
-        android:radius="8dip" />
-</shape>
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_holo_dark"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_holo_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/color/date_picker_year_selector_holo_light.xml
similarity index 64%
copy from core/res/res/drawable/lock_task_notify_bg.xml
copy to core/res/res/color/date_picker_year_selector_holo_light.xml
index 3a8fab5..c228711 100644
--- a/core/res/res/drawable/lock_task_notify_bg.xml
+++ b/core/res/res/color/date_picker_year_selector_holo_light.xml
@@ -13,10 +13,11 @@
      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" >
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <solid android:color="@android:color/black" />
-    <corners
-        android:radius="8dip" />
-</shape>
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_holo_light"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_holo_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/color/date_picker_year_selector_material_dark.xml
similarity index 64%
copy from core/res/res/drawable/lock_task_notify_bg.xml
copy to core/res/res/color/date_picker_year_selector_material_dark.xml
index 3a8fab5..b5ff09a 100644
--- a/core/res/res/drawable/lock_task_notify_bg.xml
+++ b/core/res/res/color/date_picker_year_selector_material_dark.xml
@@ -13,10 +13,11 @@
      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" >
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <solid android:color="@android:color/black" />
-    <corners
-        android:radius="8dip" />
-</shape>
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_material_dark"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_material_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/color/date_picker_year_selector_material_light.xml
similarity index 64%
copy from core/res/res/drawable/lock_task_notify_bg.xml
copy to core/res/res/color/date_picker_year_selector_material_light.xml
index 3a8fab5..5e329b3 100644
--- a/core/res/res/drawable/lock_task_notify_bg.xml
+++ b/core/res/res/color/date_picker_year_selector_material_light.xml
@@ -13,10 +13,11 @@
      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" >
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <solid android:color="@android:color/black" />
-    <corners
-        android:radius="8dip" />
-</shape>
+    <item android:state_pressed="true"
+          android:color="@color/datepicker_default_pressed_text_color_material_light"/>
+    <item android:state_pressed="false" android:state_selected="false"
+          android:color="@color/datepicker_default_normal_text_color_material_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable-hdpi/ic_recent.png b/core/res/res/drawable-hdpi/ic_recent.png
deleted file mode 100644
index 8866539..0000000
--- a/core/res/res/drawable-hdpi/ic_recent.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_recent.png b/core/res/res/drawable-mdpi/ic_recent.png
deleted file mode 100644
index 2b607df..0000000
--- a/core/res/res/drawable-mdpi/ic_recent.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_recent.png b/core/res/res/drawable-xhdpi/ic_recent.png
deleted file mode 100644
index 86316db..0000000
--- a/core/res/res/drawable-xhdpi/ic_recent.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_recent.png b/core/res/res/drawable-xxhdpi/ic_recent.png
deleted file mode 100644
index e6bd125..0000000
--- a/core/res/res/drawable-xxhdpi/ic_recent.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/drawable/background_leanback_setup.xml
similarity index 76%
rename from core/res/res/drawable/lock_task_notify_bg.xml
rename to core/res/res/drawable/background_leanback_setup.xml
index 3a8fab5..ca3392c 100644
--- a/core/res/res/drawable/lock_task_notify_bg.xml
+++ b/core/res/res/drawable/background_leanback_setup.xml
@@ -13,10 +13,7 @@
      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>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:drawable="@color/black"/>
+</layer-list>
diff --git a/core/res/res/drawable/btn_check_material_anim.xml b/core/res/res/drawable/btn_check_material_anim.xml
index 73b8a3e..1e05e84 100644
--- a/core/res/res/drawable/btn_check_material_anim.xml
+++ b/core/res/res/drawable/btn_check_material_anim.xml
@@ -30,28 +30,28 @@
     <transition android:fromId="@+id/off" android:toId="@+id/on">
         <animation-list>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_000" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_000" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_001" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_001" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_002" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_002" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_003" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_003" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_004" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_004" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_005" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_005" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_006" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_006" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_007" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_007" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
                 <bitmap android:src="@drawable/btn_check_to_on_mtrl_008" android:tint="?attr/colorControlActivated" />
@@ -106,28 +106,28 @@
                 <bitmap android:src="@drawable/btn_check_to_off_mtrl_007" android:tint="?attr/colorControlActivated" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_008" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_008" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_009" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_009" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_010" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_010" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_011" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_011" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_012" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_012" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_013" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_013" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_014" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_014" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_015" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_015" android:tint="?attr/colorControlNormal" />
             </item>
         </animation-list>
     </transition>
diff --git a/core/res/res/layout-land/date_picker_holo.xml b/core/res/res/layout-land/date_picker_holo.xml
new file mode 100644
index 0000000..98e26ca
--- /dev/null
+++ b/core/res/res/layout-land/date_picker_holo.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="@dimen/datepicker_view_animator_height"
+              android:gravity="center"
+              android:orientation="horizontal" >
+
+    <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:background="?android:attr/datePickerHeaderSelectorBackgroundColor"
+            android:orientation="vertical" >
+
+        <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="0dip"
+                android:layout_weight="1"
+                android:orientation="vertical" >
+
+            <include layout="@layout/date_picker_header_view" />
+
+            <include layout="@layout/date_picker_selected_date" />
+        </LinearLayout>
+
+        <include layout="@layout/date_picker_done_button" />
+    </LinearLayout>
+
+    <include layout="@layout/date_picker_view_animator" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout-sw600dp/date_picker_holo.xml b/core/res/res/layout-sw600dp/date_picker_holo.xml
new file mode 100644
index 0000000..847aa32
--- /dev/null
+++ b/core/res/res/layout-sw600dp/date_picker_holo.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="match_parent"
+              android:background="@color/datepicker_default_view_animator_color_holo_light"
+              android:gravity="center"
+              android:orientation="vertical" >
+
+    <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/datepicker_selected_calendar_layout_height"
+            android:orientation="vertical" >
+
+        <include layout="@layout/date_picker_header_view" />
+
+        <include layout="@layout/date_picker_selected_date" />
+    </LinearLayout>
+
+    <include layout="@layout/date_picker_view_animator" />
+
+    <include layout="@layout/date_picker_done_button" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/date_picker_done_button.xml b/core/res/res/layout/date_picker_done_button.xml
new file mode 100644
index 0000000..b8e8c03
--- /dev/null
+++ b/core/res/res/layout/date_picker_done_button.xml
@@ -0,0 +1,33 @@
+<?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/layout_buttons"
+              style="?android:attr/buttonBarStyle"
+              android:layout_width="@dimen/datepicker_component_width"
+              android:layout_height="wrap_content"
+              android:orientation="vertical"
+              android:divider="?android:attr/dividerHorizontal"
+              android:showDividers="beginning" >
+
+    <Button
+            android:id="@+id/done"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:minHeight="48dp"
+            android:text="@string/done_label"
+            android:textSize="@dimen/datepicker_done_label_size" />
+</LinearLayout>
diff --git a/core/res/res/layout/date_picker_header_view.xml b/core/res/res/layout/date_picker_header_view.xml
new file mode 100644
index 0000000..eccdd3e
--- /dev/null
+++ b/core/res/res/layout/date_picker_header_view.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@+id/date_picker_header"
+          android:layout_width="@dimen/datepicker_component_width"
+          android:layout_height="@dimen/datepicker_header_height"
+          android:background="?android:attr/datePickerHeaderDayOfWeekLabelBackgroundColor"
+          android:gravity="center"
+          android:textAppearance="?android:attr/datePickerHeaderDayOfWeekLabelTextAppearance"
+          android:importantForAccessibility="no"
+          android:textAllCaps="true"
+          />
diff --git a/core/res/res/layout/date_picker_holo.xml b/core/res/res/layout/date_picker_holo.xml
index b465d97..389c2b5 100644
--- a/core/res/res/layout/date_picker_holo.xml
+++ b/core/res/res/layout/date_picker_holo.xml
@@ -1,92 +1,37 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-**
-** Copyright 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.
-*/
--->
+     Copyright (C) 2011 The Android Open Source Project
 
-<!-- Layout of date picker-->
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
 
-<!-- Warning: everything within the "pickers" layout is removed and re-ordered
-     depending on the date format selected by the user.
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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_horizontal"
-    android:orientation="horizontal"
-    android:gravity="center">
+              android:layout_width="@dimen/datepicker_component_width"
+              android:layout_height="match_parent"
+              android:gravity="center"
+              android:orientation="vertical" >
 
-    <LinearLayout android:id="@+id/pickers"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="horizontal"
-        android:gravity="center">
-
-        <!-- Month -->
-        <NumberPicker
-            android:id="@+id/month"
+    <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginTop="16dip"
-            android:layout_marginBottom="16dip"
-            android:layout_marginStart="8dip"
-            android:layout_marginEnd="8dip"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            />
+            android:orientation="vertical" >
 
-        <!-- Day -->
-        <NumberPicker
-            android:id="@+id/day"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="16dip"
-            android:layout_marginBottom="16dip"
-            android:layout_marginStart="8dip"
-            android:layout_marginEnd="8dip"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            />
+        <include layout="@layout/date_picker_header_view" />
 
-        <!-- Year -->
-        <NumberPicker
-            android:id="@+id/year"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="16dip"
-            android:layout_marginBottom="16dip"
-            android:layout_marginStart="8dip"
-            android:layout_marginEnd="16dip"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            />
-
+        <include layout="@layout/date_picker_selected_date" />
     </LinearLayout>
 
-    <!-- calendar view -->
-    <CalendarView
-        android:id="@+id/calendar_view"
-        android:layout_width="245dip"
-        android:layout_height="280dip"
-        android:layout_marginStart="16dip"
-        android:layout_marginEnd="16dip"
-        android:layout_weight="1"
-        android:focusable="true"
-        android:focusableInTouchMode="true"
-        />
+    <include layout="@layout/date_picker_view_animator" />
 
-</LinearLayout>
+    <include layout="@layout/date_picker_done_button" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker_legacy.xml
similarity index 100%
rename from core/res/res/layout/date_picker.xml
rename to core/res/res/layout/date_picker_legacy.xml
diff --git a/core/res/res/layout/date_picker_legacy_holo.xml b/core/res/res/layout/date_picker_legacy_holo.xml
new file mode 100644
index 0000000..b465d97
--- /dev/null
+++ b/core/res/res/layout/date_picker_legacy_holo.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- Layout of date picker-->
+
+<!-- Warning: everything within the "pickers" layout is removed and re-ordered
+     depending on the date format selected by the user.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal"
+    android:orientation="horizontal"
+    android:gravity="center">
+
+    <LinearLayout android:id="@+id/pickers"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        android:gravity="center">
+
+        <!-- Month -->
+        <NumberPicker
+            android:id="@+id/month"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dip"
+            android:layout_marginBottom="16dip"
+            android:layout_marginStart="8dip"
+            android:layout_marginEnd="8dip"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            />
+
+        <!-- Day -->
+        <NumberPicker
+            android:id="@+id/day"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dip"
+            android:layout_marginBottom="16dip"
+            android:layout_marginStart="8dip"
+            android:layout_marginEnd="8dip"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            />
+
+        <!-- Year -->
+        <NumberPicker
+            android:id="@+id/year"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dip"
+            android:layout_marginBottom="16dip"
+            android:layout_marginStart="8dip"
+            android:layout_marginEnd="16dip"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            />
+
+    </LinearLayout>
+
+    <!-- calendar view -->
+    <CalendarView
+        android:id="@+id/calendar_view"
+        android:layout_width="245dip"
+        android:layout_height="280dip"
+        android:layout_marginStart="16dip"
+        android:layout_marginEnd="16dip"
+        android:layout_weight="1"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        />
+
+</LinearLayout>
diff --git a/core/res/res/layout/date_picker_selected_date.xml b/core/res/res/layout/date_picker_selected_date.xml
new file mode 100644
index 0000000..426deed
--- /dev/null
+++ b/core/res/res/layout/date_picker_selected_date.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:id="@+id/day_picker_selector_layout"
+              android:layout_width="@dimen/datepicker_component_width"
+              android:layout_height="0dip"
+              android:layout_weight="1"
+              android:paddingTop="8dip"
+              android:paddingBottom="8dip"
+              android:background="?android:attr/datePickerHeaderSelectorBackgroundColor"
+              android:gravity="center"
+              android:orientation="vertical" >
+
+    <LinearLayout
+            android:id="@+id/date_picker_month_and_day_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:clickable="true"
+            android:orientation="vertical" >
+
+        <TextView
+                android:id="@+id/date_picker_month"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:duplicateParentState="true"
+                android:gravity="center_horizontal|bottom"
+                android:textAppearance="?android:attr/datePickerHeaderSelectorMonthLabelTextAppearance" />
+
+        <TextView
+                android:id="@+id/date_picker_day"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:layout_marginBottom="-10dip"
+                android:layout_marginTop="-10dip"
+                android:duplicateParentState="true"
+                android:gravity="center"
+                android:textAppearance="?android:attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance" />
+    </LinearLayout>
+
+    <TextView
+            android:id="@+id/date_picker_year"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center_horizontal|top"
+            android:textAppearance="?android:attr/datePickerHeaderSelectorYearLabelTextAppearance" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/layout/date_picker_view_animator.xml
similarity index 66%
copy from core/res/res/drawable/lock_task_notify_bg.xml
copy to core/res/res/layout/date_picker_view_animator.xml
index 3a8fab5..9085ed5 100644
--- a/core/res/res/drawable/lock_task_notify_bg.xml
+++ b/core/res/res/layout/date_picker_view_animator.xml
@@ -13,10 +13,9 @@
      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>
+<com.android.internal.widget.AccessibleDateAnimator
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/animator"
+        android:layout_width="@dimen/datepicker_component_width"
+        android:layout_height="@dimen/datepicker_view_animator_height"
+        android:gravity="center" />
\ No newline at end of file
diff --git a/core/res/res/layout/lock_to_app_checkbox.xml b/core/res/res/layout/lock_to_app_checkbox.xml
new file mode 100644
index 0000000..890507b
--- /dev/null
+++ b/core/res/res/layout/lock_to_app_checkbox.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.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.
+*/
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="15dip"
+    android:paddingBottom="0dip"
+    android:paddingStart="12dip"
+    android:paddingEnd="25dip"
+    >
+
+    <CheckBox
+        android:id="@+id/lock_to_app_checkbox"
+        style="?android:attr/textAppearanceMedium"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</FrameLayout>
diff --git a/core/res/res/layout/lock_to_app_enter.xml b/core/res/res/layout/lock_to_app_enter.xml
deleted file mode 100644
index c034536..0000000
--- a/core/res/res/layout/lock_to_app_enter.xml
+++ /dev/null
@@ -1,44 +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.
--->
-<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
deleted file mode 100644
index 4a60c80..0000000
--- a/core/res/res/layout/lock_to_app_exit.xml
+++ /dev/null
@@ -1,45 +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.
--->
-<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/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index b3a3478..5acb588 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -35,7 +35,6 @@
         android:layout_alignParentTop="true"
         style="?attr/actionBarStyle"
         android:transitionName="android:action_bar"
-        android:touchscreenBlocksFocus="true"
         android:gravity="top">
         <com.android.internal.widget.ActionBarView
             android:id="@+id/action_bar"
@@ -54,6 +53,5 @@
                   android:layout_height="wrap_content"
                   style="?attr/actionBarSplitStyle"
                   android:visibility="gone"
-                  android:touchscreenBlocksFocus="true"
                   android:gravity="center"/>
 </com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml
index 039e89f..56815f8 100644
--- a/core/res/res/layout/screen_toolbar.xml
+++ b/core/res/res/layout/screen_toolbar.xml
@@ -35,7 +35,6 @@
         android:layout_alignParentTop="true"
         style="?attr/actionBarStyle"
         android:transitionName="android:action_bar"
-        android:touchscreenBlocksFocus="true"
         android:gravity="top">
         <Toolbar
             android:id="@+id/action_bar"
diff --git a/core/res/res/layout/year_label_text_view.xml b/core/res/res/layout/year_label_text_view.xml
new file mode 100644
index 0000000..4e39831
--- /dev/null
+++ b/core/res/res/layout/year_label_text_view.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<android.widget.TextViewWithCircularIndicator
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/month_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/datepicker_year_label_height"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:textAppearance="?android:attr/datePickerHeaderListYearLabelTextAppearance" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ed228e4..93d59b4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -691,6 +691,33 @@
         <!-- The DatePicker style. -->
         <attr name="datePickerStyle" format="reference" />
 
+        <!-- The DatePicker Header day of week label background color . -->
+        <attr name="datePickerHeaderDayOfWeekLabelBackgroundColor" format="reference" />
+
+        <!-- The DatePicker Header day of week label text appearance -->
+        <attr name="datePickerHeaderDayOfWeekLabelTextAppearance" format="reference" />
+
+        <!-- The DatePicker Header selector background color . -->
+        <attr name="datePickerHeaderSelectorBackgroundColor" format="reference" />
+
+        <!-- The DatePicker Header selector month label text appearance -->
+        <attr name="datePickerHeaderSelectorMonthLabelTextAppearance" format="reference" />
+
+        <!-- The DatePicker Header selector day of month label text appearance -->
+        <attr name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance" format="reference" />
+
+        <!-- The DatePicker Header selector year label text appearance -->
+        <attr name="datePickerHeaderSelectorYearLabelTextAppearance" format="reference" />
+
+        <!-- The DatePicker Header list year label text appearance -->
+        <attr name="datePickerHeaderListYearLabelTextAppearance" format="reference" />
+
+        <!-- DatePicker list year label circle background color -->
+        <attr name="datePickerHeaderListYearLabelCircleBackgroundColor" format="reference" />
+
+        <!-- The DatePicker dialog theme. -->
+        <attr name="datePickerDialogTheme" format="reference" />
+
         <!-- Default ActivityChooserView style. -->
         <attr name="activityChooserViewStyle" format="reference" />
 
@@ -1892,6 +1919,14 @@
         <!--  When opening an activity in a new task, this is the animation that is
               run on the activity of the old task (which is exiting the screen). -->
         <attr name="taskOpenExitAnimation" format="reference" />
+        <!--  When opening an activity in a new task using Intent/FLAG_ACTIVITY_LAUNCH_BEHIND,
+              this is the animation that is run on the activity of the new task (which is
+              entering the screen and then leaving). -->
+        <attr name="launchTaskBehindBackgroundAnimation" format="reference" />
+        <!--  When opening an activity in a new task using Intent.FLAG_ACTIVITY_LAUNCH_BEHIND,
+              this is the animation that is run on the activity of the old task (which is
+              already on the screen and then stays on). -->
+        <attr name="launchTaskBehindSourceAnimation" format="reference" />
         <!--  When closing the last activity of a task, this is the animation that is
               run on the activity of the next task (which is entering the screen). -->
         <attr name="taskCloseEnterAnimation" format="reference" />
@@ -4175,6 +4210,28 @@
         <attr name="maxDate" format="string" />
         <!-- @hide The layout of the date picker. -->
         <attr name="internalLayout" format="reference"  />
+        <!-- @hide The layout of the legacy DatePicker. -->
+        <attr name="legacyLayout" />
+        <!-- @hide Enables or disable the use of the legacy layout for the DatePicker. -->
+        <attr name="legacyMode" />
+        <!-- The background color for the date selector 's day of week of the non legacy DatePicker. -->
+        <attr name="dateSelectorDayOfWeekBackgroundColor" format="color|reference" />
+        <!-- The text color for the date selector's day of week of the non legacy DatePicker. -->
+        <attr name="dateSelectorDayOfWeekTextAppearance" format="reference" />
+        <!-- The background color for the date selector of the non legacy DatePicker. -->
+        <attr name="dateSelectorBackgroundColor" format="color|reference" />
+        <!-- The month's text appearance in the date selector of the non legacy DatePicker. -->
+        <attr name="dateSelectorMonthTextAppearance" format="reference" />
+        <!-- The day of month's text appearance in the date selector of the non legacy DatePicker. -->
+        <attr name="dateSelectorDayOfMonthTextAppearance" format="reference" />
+        <!-- The year's text appearance in the date selector of the non legacy DatePicker. -->
+        <attr name="dateSelectorYearTextAppearance" format="reference" />
+        <!-- The list year's text appearance in the list of the non legacy DatePicker. -->
+        <attr name="dateSelectorYearListItemTextAppearance" format="reference" />
+        <!-- The list year's selected circle color in the list of the non legacy DatePicker. -->
+        <attr name="dateSelectorYearListSelectedCircleColor" format="color|reference" />
+        <!-- The text color list of the calendar of the non legacy DatePicker. -->
+        <attr name="calendarTextColor" format="color|reference" />
     </declare-styleable>
 
     <declare-styleable name="TwoLineListItem">
@@ -7161,4 +7218,21 @@
         <attr name="ambientShadowAlpha" format="float" />
         <attr name="spotShadowAlpha" format="float" />
     </declare-styleable>
+
+    <declare-styleable name="RestrictionEntry">
+        <attr name="key" />
+        <attr name="restrictionType">
+            <enum name="hidden" value="0" />
+            <enum name="bool" value="1" />
+            <enum name="choice" value="2" />
+            <enum name="multi-select" value="4" />
+            <enum name="integer" value="5" />
+            <enum name="string" value="6" />
+        </attr>
+        <attr name="title" />
+        <attr name="description" />
+        <attr name="defaultValue" />
+        <attr name="entries" />
+        <attr name="entryValues" />
+    </declare-styleable>
 </resources>
diff --git a/core/res/res/values/colors_holo.xml b/core/res/res/values/colors_holo.xml
index 97b4803..8785a567 100644
--- a/core/res/res/values/colors_holo.xml
+++ b/core/res/res/values/colors_holo.xml
@@ -120,4 +120,32 @@
 
     <color name="timepicker_default_ampm_unselected_background_color_holo_light">@color/white</color>
     <color name="timepicker_default_ampm_unselected_background_color_holo_dark">@color/transparent</color>
+
+    <!-- DatePicker colors -->
+    <eat-comment />
+
+    <color name="datepicker_default_header_selector_background_holo_light">@android:color/white</color>
+    <color name="datepicker_default_header_selector_background_holo_dark">#ff303030</color>
+
+    <color name="datepicker_default_header_dayofweek_background_color_holo_light">#999999</color>
+    <color name="datepicker_default_header_dayofweek_background_color_holo_dark">@android:color/white</color>
+
+    <color name="datepicker_default_normal_text_color_holo_light">#ff999999</color>
+    <color name="datepicker_default_normal_text_color_holo_dark">@android:color/white</color>
+
+    <color name="datepicker_default_disabled_text_color_holo_light">#80999999</color>
+    <color name="datepicker_default_disabled_text_color_holo_dark">#80999999</color>
+
+    <color name="datepicker_default_selected_text_color_holo_light">#33b5e5</color>
+    <color name="datepicker_default_selected_text_color_holo_dark">#33b5e5</color>
+
+    <color name="datepicker_default_pressed_text_color_holo_light">#0099cc</color>
+    <color name="datepicker_default_pressed_text_color_holo_dark">#0099cc</color>
+
+    <color name="datepicker_default_circle_background_color_holo_light">@android:color/holo_blue_light</color>
+    <color name="datepicker_default_circle_background_color_holo_dark">@android:color/holo_blue_light</color>
+
+    <color name="datepicker_default_view_animator_color_holo_light">#f2f2f2</color>
+    <color name="datepicker_default_view_animator_color_holo_dark">#ff303030</color>
+
 </resources>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 7371d4e..c4f4891a 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -166,4 +166,32 @@
     <color name="timepicker_default_ampm_unselected_background_color_material">@color/transparent</color>
     <color name="timepicker_default_selector_color_material">@color/material_light_blue_A200</color>
     <color name="timepicker_default_numbers_background_color_material">@color/transparent</color>
+
+    <!-- DatePicker colors -->
+    <eat-comment />
+
+    <color name="datepicker_default_header_selector_background_material_light">@android:color/white</color>
+    <color name="datepicker_default_header_selector_background_material_dark">#ff303030</color>
+
+    <color name="datepicker_default_header_dayofweek_background_color_material_light">#999999</color>
+    <color name="datepicker_default_header_dayofweek_background_color_material_dark">@android:color/white</color>
+
+    <color name="datepicker_default_normal_text_color_material_light">#ff999999</color>
+    <color name="datepicker_default_normal_text_color_material_dark">@android:color/white</color>
+
+    <color name="datepicker_default_disabled_text_color_material_light">#80999999</color>
+    <color name="datepicker_default_disabled_text_color_material_dark">#80999999</color>
+
+    <color name="datepicker_default_selected_text_color_material_light">#33b5e5</color>
+    <color name="datepicker_default_selected_text_color_material_dark">#33b5e5</color>
+
+    <color name="datepicker_default_pressed_text_color_material_light">#0099cc</color>
+    <color name="datepicker_default_pressed_text_color_material_dark">#0099cc</color>
+
+    <color name="datepicker_default_circle_background_color_material_light">@android:color/holo_blue_light</color>
+    <color name="datepicker_default_circle_background_color_material_dark">@android:color/holo_blue_light</color>
+
+    <color name="datepicker_default_view_animator_color_material_light">#f2f2f2</color>
+    <color name="datepicker_default_view_animator_color_material_dark">#ff303030</color>
+
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c9c29ca..0954ddf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -564,6 +564,12 @@
         <item>5</item>
     </integer-array>
 
+    <!-- Vibrator pattern for feedback when selecting a day/month/year date of a Calendar -->
+    <integer-array name="config_calendarDateVibePattern">
+        <item>125</item>
+        <item>5</item>
+    </integer-array>
+
     <!-- Vibrator pattern for feedback about booting with safe mode disabled -->
     <integer-array name="config_safeModeDisabledVibePattern">
         <item>0</item>
@@ -1513,6 +1519,7 @@
      See {@link com.android.server.notification.NotificationSignalExtractor} -->
     <string-array name="config_notificationSignalExtractors">
         <item>com.android.server.notification.ValidateNotificationPeople</item>
+        <item>com.android.server.notification.PackagePriorityExtractor</item>
         <item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
     </string-array>
 
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ad6c6cd7..77b115b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -362,6 +362,24 @@
     <dimen name="timepicker_minimum_margin_top_bottom">24dip</dimen>
     <dimen name="timepicker_radial_picker_dimen">270dip</dimen>
 
+    <dimen name="datepicker_done_label_size">14sp</dimen>
+    <dimen name="datepicker_day_number_size">16sp</dimen>
+    <dimen name="datepicker_month_label_size">16sp</dimen>
+    <dimen name="datepicker_month_day_label_text_size">10sp</dimen>
+    <dimen name="datepicker_day_number_select_circle_radius">16dp</dimen>
+    <dimen name="datepicker_month_list_item_header_height">50dp</dimen>
+    <dimen name="datepicker_view_animator_height">270dp</dimen>
+    <dimen name="datepicker_year_picker_padding_top">8dp</dimen>
+    <dimen name="datepicker_year_label_height">64dp</dimen>
+    <dimen name="datepicker_year_label_text_size">22dp</dimen>
+    <dimen name="datepicker_component_width">270dp</dimen>
+    <dimen name="datepicker_selected_calendar_layout_height">155dp</dimen>
+    <dimen name="datepicker_selected_date_day_size">75dp</dimen>
+    <dimen name="datepicker_selected_date_month_size">30dp</dimen>
+    <dimen name="datepicker_selected_date_year_size">30dp</dimen>
+    <dimen name="datepicker_header_height">30dp</dimen>
+    <dimen name="datepicker_header_text_size">14dp</dimen>
+
     <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
     <dimen name="immersive_mode_cling_width">-1px</dimen>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fee5c43..7b9a38f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2236,6 +2236,20 @@
   <public type="attr" name="multiArch" />
   <public type="attr" name="touchscreenBlocksFocus" />
   <public type="attr" name="windowElevation" />
+  <public type="attr" name="launchTaskBehindBackgroundAnimation" />
+  <public type="attr" name="launchTaskBehindSourceAnimation" />
+  <!-- Attribute specified in a restriction entry to denote the type of restriction. -->
+  <public type="attr" name="restrictionType" />
+
+  <public type="attr" name="dateSelectorDayOfWeekBackgroundColor" />
+  <public type="attr" name="dateSelectorDayOfWeekTextAppearance" />
+  <public type="attr" name="dateSelectorBackgroundColor" />
+  <public type="attr" name="dateSelectorMonthTextAppearance" />
+  <public type="attr" name="dateSelectorDayOfMonthTextAppearance" />
+  <public type="attr" name="dateSelectorYearTextAppearance" />
+  <public type="attr" name="dateSelectorYearListItemTextAppearance" />
+  <public type="attr" name="dateSelectorYearListSelectedCircleColor" />
+  <public type="attr" name="calendarTextColor" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
@@ -2483,6 +2497,8 @@
 
   <public-padding type="interpolator" name="l_resource_pad" end="0x010c0010" />
 
+  <public type="style" name="Theme.Leanback.FormWizard"/>
+
   <!-- An interpolator which accelerates fast but decelerates slowly. -->
   <public type="interpolator" name="fast_out_slow_in" />
   <!-- An interpolator which starts with a peak non-zero velocity and decelerates slowly. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2ecaa5c..0285f54 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4792,21 +4792,31 @@
     <!-- DO NOT TRANSLATE -->
     <string name="day_of_week_label_typeface">sans-serif</string>
 
-    <!-- Lock-to-app notification toast. The $ is not actually shown or translated, it is a marker of where the recents icon shows up. -->
-    <string name="lock_to_app_toast">You are in Lock-to-App mode. Press and hold the recent apps button to exit $</string>
+    <!-- Notify use that they are in Lock-to-app -->
+    <string name="lock_to_app_toast">You are in lock-to-app mode. To exit, touch and hold the Recents button</string>
     <!-- Notify user that they are locked in lock-to-app mode -->
     <string name="lock_to_app_toast_locked">You are in Lock-to-App mode.</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 dialog description. -->
+    <string name="lock_to_app_description">Lock-to-app locks the display in a single app.\n\nTo exit, touch and hold the Recents button.</string>
     <!-- Lock-to-app negative response. -->
-    <string name="lock_to_app_negative">NO</string>
+    <string name="lock_to_app_negative">NO, THANKS</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>
+    <string name="lock_to_app_start">Locked to app</string>
     <!-- Exting lock-to-app indication. -->
-    <string name="lock_to_app_exit">Exit Lock-to-app</string>
+    <string name="lock_to_app_exit">No longer locked to app</string>
+
+    <!-- Lock-to-app checkbox for lock on exit -->
+    <string name="lock_to_app_use_screen_lock">Ask for %1$s before exiting</string>
+
+    <!-- Lock-to-app unlock pin string -->
+    <string name="lock_to_app_unlock_pin">PIN</string>
+    <!-- Lock-to-app unlock pattern string -->
+    <string name="lock_to_app_unlock_pattern">unlock pattern</string>
+    <!-- Lock-to-app unlock password string -->
+    <string name="lock_to_app_unlock_password">password</string>
 
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 32065a18..94ac19c 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -82,6 +82,8 @@
         <item name="activityCloseExitAnimation">@anim/activity_close_exit</item>
         <item name="taskOpenEnterAnimation">@anim/task_open_enter</item>
         <item name="taskOpenExitAnimation">@anim/task_open_exit</item>
+        <item name="launchTaskBehindBackgroundAnimation">@anim/launch_task_behind_background</item>
+        <item name="launchTaskBehindSourceAnimation">@anim/launch_task_behind_source</item>
         <item name="taskCloseEnterAnimation">@anim/task_close_enter</item>
         <item name="taskCloseExitAnimation">@anim/task_close_exit</item>
         <item name="taskToFrontEnterAnimation">@anim/task_open_enter</item>
@@ -570,7 +572,7 @@
     </style>
 
     <style name="Widget.DatePicker">
-        <item name="internalLayout">@layout/date_picker</item>
+        <item name="legacyLayout">@android:layout/date_picker_legacy</item>
         <item name="calendarViewShown">false</item>
     </style>
 
@@ -1200,7 +1202,6 @@
         <item name="navigationButtonStyle">@style/Widget.Toolbar.Button.Navigation</item>
         <item name="collapseIcon">?attr/homeAsUpIndicator</item>
         <item name="contentInsetStart">16dp</item>
-        <item name="touchscreenBlocksFocus">true</item>
     </style>
 
     <style name="Widget.Toolbar.Button.Navigation" parent="Widget">
@@ -1336,11 +1337,6 @@
     <style name="TextAppearance.TimePicker.AmPmLabel" parent="TextAppearance">
     </style>
 
-    <style name="TextAppearance.Holo.TimePicker.TimeLabel" parent="TextAppearance.Holo">
-        <item name="textSize">@dimen/timepicker_time_label_size</item>
-        <item name="textColor">@color/timepicker_default_text_color_holo_dark</item>
-    </style>
-
     <style name="Widget.FastScroll">
         <item name="thumbDrawable">?attr/fastScrollThumbDrawable</item>
         <item name="trackDrawable">?attr/fastScrollTrackDrawable</item>
@@ -1364,4 +1360,22 @@
         <item name="spotShadowAlpha">0.1765</item>
     </style>
 
+    <style name="TextAppearance.DatePicker.DayOfWeekLabel" parent="TextAppearance">
+    </style>
+
+    <style name="TextAppearance.DatePicker.Selector" parent="TextAppearance">
+    </style>
+
+    <style name="TextAppearance.DatePicker.Selector.MonthLabel" parent="TextAppearance.DatePicker.Selector">
+    </style>
+
+    <style name="TextAppearance.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.DatePicker.Selector">
+    </style>
+
+    <style name="TextAppearance.DatePicker.Selector.YearLabel" parent="TextAppearance.DatePicker.Selector">
+    </style>
+
+    <style name="TextAppearance.DatePicker.List.YearLabel" parent="TextAppearance">
+    </style>
+
 </resources>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 84d38ce..9b5629b 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -280,4 +280,29 @@
     <style name="DeviceDefault.Light.ButtonBar" parent="Widget.Material.Light.ButtonBar"/>
     <style name="DeviceDefault.Light.ButtonBar.AlertDialog" parent="Widget.Material.Light.ButtonBar.AlertDialog"/>
     <style name="DeviceDefault.Light.SegmentedButton" parent="Widget.Material.Light.SegmentedButton"/>
+
+    <style name="TextAppearance.DeviceDefault.Light.TimePicker.TimeLabel" parent="TextAppearance.Material.TimePicker.TimeLabel"/>
+    <style name="TextAppearance.DeviceDefault.Light.TimePicker.AmPmLabel" parent="TextAppearance.Material.TimePicker.AmPmLabel"/>
+
+    <style name="Theme.DeviceDefault.Dialog.TimePicker" parent="Theme.Material.Dialog.TimePicker"/>
+    <style name="Theme.DeviceDefault.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.TimePicker"/>
+
+    <style name="TextAppearance.DeviceDefault.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material.DatePicker.DayOfWeekLabel"/>
+    <style name="TextAppearance.DeviceDefault.Light.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material.Light.DatePicker.DayOfWeekLabel"/>
+
+    <style name="TextAppearance.DeviceDefault.DatePicker.Selector" parent="TextAppearance.Material.DatePicker.Selector"/>
+    <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector" parent="TextAppearance.Material.Light.DatePicker.Selector"/>
+
+    <style name="TextAppearance.DeviceDefault.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.DatePicker.Selector.MonthLabel"/>
+    <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector.MonthLabel"/>
+
+    <style name="TextAppearance.DeviceDefault.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.DatePicker.Selector.DayOfMonthLabel"/>
+    <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector.DayOfMonthLabel"/>
+
+    <style name="TextAppearance.DeviceDefault.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.DatePicker.Selector.YearLabel"/>
+    <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.Light.DatePicker.Selector.YearLabel"/>
+
+    <style name="Theme.DeviceDefault.Dialog.DatePicker" parent="Theme.Material.Dialog.DatePicker"/>
+    <style name="Theme.DeviceDefault.Light.Dialog.DatePicker" parent="Theme.Material.Light.Dialog.DatePicker"/>
+
 </resources>
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index 327d6b5..5dfbaed 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -477,8 +477,18 @@
     </style>
 
     <style name="Widget.Holo.DatePicker" parent="Widget.DatePicker">
+        <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
         <item name="internalLayout">@layout/date_picker_holo</item>
         <item name="calendarViewShown">true</item>
+        <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+        <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+        <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+        <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+        <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+        <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+        <item name="calendarTextColor">@color/date_picker_calendar_holo_dark</item>
     </style>
 
     <style name="Widget.Holo.ActivityChooserView" parent="Widget.ActivityChooserView" />
@@ -884,7 +894,20 @@
         <item name="numbersSelectorColor">@color/holo_blue_light</item>
     </style>
 
-    <style name="Widget.Holo.Light.DatePicker" parent="Widget.Holo.DatePicker" />
+    <style name="Widget.Holo.Light.DatePicker" parent="Widget.DatePicker">
+        <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
+        <item name="internalLayout">@layout/date_picker_holo</item>
+        <item name="calendarViewShown">true</item>
+        <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+        <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+        <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+        <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+        <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+        <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+        <item name="calendarTextColor">@color/date_picker_calendar_holo_light</item>
+    </style>
 
     <style name="Widget.Holo.Light.ActivityChooserView" parent="Widget.Holo.ActivityChooserView">
         <item name="background">@drawable/ab_share_pack_holo_light</item>
@@ -1183,6 +1206,11 @@
         <item name="externalRouteEnabledDrawable">@drawable/ic_media_route_holo_light</item>
     </style>
 
+    <style name="TextAppearance.Holo.TimePicker.TimeLabel" parent="TextAppearance.Holo">
+        <item name="textSize">@dimen/timepicker_time_label_size</item>
+        <item name="textColor">@color/timepicker_default_text_color_holo_dark</item>
+    </style>
+
     <style name="TextAppearance.Holo.TimePicker.AmPmLabel" parent="TextAppearance.Holo">
         <item name="textSize">@dimen/timepicker_ampm_label_size</item>
         <item name="textAllCaps">true</item>
@@ -1202,6 +1230,62 @@
         <item name="textStyle">bold</item>
     </style>
 
+    <style name="TextAppearance.Holo.DatePicker.DayOfWeekLabel" parent="TextAppearance.Holo">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/black</item>
+        <item name="textSize">@dimen/datepicker_header_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.DatePicker.Selector" parent="TextAppearance.Holo">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/date_picker_selector_holo_dark</item>
+    </style>
+
+    <style name="TextAppearance.Holo.DatePicker.Selector.MonthLabel" parent="TextAppearance.Holo.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Holo.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.DatePicker.Selector.YearLabel" parent="TextAppearance.Holo.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.DatePicker.List.YearLabel" parent="TextAppearance.Holo">
+        <item name="textColor">@color/date_picker_year_selector_holo_dark</item>
+        <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.DatePicker.DayOfWeekLabel" parent="TextAppearance.Holo">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/white</item>
+        <item name="textSize">@dimen/datepicker_header_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.DatePicker.Selector" parent="TextAppearance.Holo">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/date_picker_selector_holo_light</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.DatePicker.Selector.MonthLabel" parent="TextAppearance.Holo.Light.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Holo.Light.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.DatePicker.Selector.YearLabel" parent="TextAppearance.Holo.Light.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+    </style>
+
+    <style name="TextAppearance.Holo.Light.DatePicker.List.YearLabel" parent="TextAppearance.Holo">
+        <item name="textColor">@color/date_picker_year_selector_holo_light</item>
+        <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+    </style>
+
     <style name="Widget.Holo.FastScroll" parent="Widget.FastScroll">
         <item name="thumbMinWidth">0dp</item>
         <item name="thumbMinHeight">0dp</item>
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index 6f9a88e..5531433 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -30,4 +30,30 @@
         <item name="hideWheelUntilFocused">true</item>
     </style>
 
+    <!-- Setup and form wizard themes -->
+    <style name="TextAppearance.Leanback.FormWizard" parent="@style/TextAppearance.Material">
+        <item name="textSize">18sp</item>
+        <item name="fontFamily">sans-serif-light</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.FormWizard.Small" parent="@style/TextAppearance.Material.Small">
+        <item name="textSize">18sp</item>
+        <item name="fontFamily">sans-serif-light</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.FormWizard.Medium" parent="@style/TextAppearance.Material.Medium">
+        <item name="textSize">36sp</item>
+        <item name="fontFamily">sans-serif-thin</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.FormWizard.Large" parent="@style/TextAppearance.Material.Large">
+        <item name="textSize">56sp</item>
+        <item name="fontFamily">sans-serif-thin</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.FormWizard.ListItem" parent="@style/TextAppearance.Material.Subhead">
+        <item name="textSize">18sp</item>
+        <item name="fontFamily">sans-serif-condensed</item>
+    </style>
+
 </resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 4623258..1d07c8d02 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -353,6 +353,62 @@
         <item name="textStyle">bold</item>
     </style>
 
+    <style name="TextAppearance.Material.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/black</item>
+        <item name="textSize">@dimen/datepicker_header_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.DatePicker.Selector" parent="TextAppearance.Material">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/date_picker_selector_material_dark</item>
+    </style>
+
+    <style name="TextAppearance.Material.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.DatePicker.List.YearLabel" parent="TextAppearance.Material">
+        <item name="textColor">@color/date_picker_year_selector_material_dark</item>
+        <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.Light.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/white</item>
+        <item name="textSize">@dimen/datepicker_header_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.Light.DatePicker.Selector" parent="TextAppearance.Material">
+        <item name="includeFontPadding">false</item>
+        <item name="textColor">@color/date_picker_selector_material_light</item>
+    </style>
+
+    <style name="TextAppearance.Material.Light.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.Light.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.Light.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.Light.DatePicker.Selector">
+        <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.Light.DatePicker.List.YearLabel" parent="TextAppearance.Material">
+        <item name="textColor">@color/date_picker_year_selector_material_light</item>
+        <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+    </style>
+
     <style name="TextAppearance.StatusBar.Material" />
 
     <style name="TextAppearance.StatusBar.Material.EventContent">
@@ -575,8 +631,18 @@
     </style>
 
     <style name="Widget.Material.DatePicker" parent="Widget.DatePicker">
+        <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
         <item name="internalLayout">@layout/date_picker_holo</item>
         <item name="calendarViewShown">true</item>
+        <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+        <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+        <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+        <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+        <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+        <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+        <item name="calendarTextColor">@color/date_picker_calendar_holo_dark</item>
     </style>
 
     <style name="Widget.Material.ActivityChooserView" parent="Widget.ActivityChooserView">
@@ -897,7 +963,21 @@
         <item name="disabledColor">@color/bright_foreground_disabled_material_light</item>
     </style>
 
-    <style name="Widget.Material.Light.DatePicker" parent="Widget.Material.DatePicker"/>
+    <style name="Widget.Material.Light.DatePicker" parent="Widget.DatePicker">
+        <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
+        <item name="internalLayout">@layout/date_picker_holo</item>
+        <item name="calendarViewShown">true</item>
+        <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+        <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+        <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+        <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+        <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+        <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+        <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+        <item name="calendarTextColor">@color/date_picker_calendar_holo_light</item>
+    </style>
+
     <style name="Widget.Material.Light.ActivityChooserView" parent="Widget.Material.ActivityChooserView" />
     <style name="Widget.Material.Light.ImageWell" parent="Widget.Material.ImageWell"/>
     <style name="Widget.Material.Light.ListView" parent="Widget.Material.ListView"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 29829e8..46e0194 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -611,10 +611,14 @@
   <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="layout" name="lock_to_app_checkbox" />
+  <java-symbol type="id" name="lock_to_app_checkbox" />
+  <java-symbol type="string" name="lock_to_app_start" />
+  <java-symbol type="string" name="lock_to_app_exit" />
+  <java-symbol type="string" name="lock_to_app_use_screen_lock" />
+  <java-symbol type="string" name="lock_to_app_unlock_pin" />
+  <java-symbol type="string" name="lock_to_app_unlock_pattern" />
+  <java-symbol type="string" name="lock_to_app_unlock_password" />
   <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" />
@@ -1182,7 +1186,7 @@
   <java-symbol type="layout" name="calendar_view" />
   <java-symbol type="layout" name="character_picker" />
   <java-symbol type="layout" name="character_picker_button" />
-  <java-symbol type="layout" name="date_picker" />
+  <java-symbol type="layout" name="date_picker_legacy" />
   <java-symbol type="layout" name="date_picker_dialog" />
   <java-symbol type="layout" name="expanded_menu_layout" />
   <java-symbol type="layout" name="fragment_bread_crumb_item" />
@@ -1840,9 +1844,10 @@
   <java-symbol type="dimen" name="subtitle_shadow_offset" />
   <java-symbol type="dimen" name="subtitle_outline_width" />
 
-  <!-- From the new TimePicker -->
-  <java-symbol type="attr" name="timePickerHeaderBackgroundColor" />
+  <!-- From the new TimePicker and DatePicker -->
   <java-symbol type="attr" name="timePickerDialogTheme" />
+  <java-symbol type="attr" name="timePickerHeaderTimeLabelTextAppearance" />
+  <java-symbol type="attr" name="timePickerHeaderBackgroundColor" />
   <java-symbol type="attr" name="headerSelectedTextColor" />
   <java-symbol type="attr" name="headerUnselectedTextColor" />
   <java-symbol type="attr" name="numbersTextColor" />
@@ -1853,9 +1858,18 @@
   <java-symbol type="attr" name="numbersSelectorColor" />
   <java-symbol type="attr" name="timePickerHeaderTimeLabelTextAppearance" />
   <java-symbol type="attr" name="nestedScrollingEnabled" />
+  <java-symbol type="attr" name="datePickerDialogTheme" />
+  <java-symbol type="attr" name="datePickerHeaderSelectorBackgroundColor" />
+  <java-symbol type="attr" name="datePickerHeaderListYearLabelCircleBackgroundColor" />
+  <java-symbol type="attr" name="calendarTextColor" />
+
   <java-symbol type="style" name="TextAppearance.Holo.TimePicker.TimeLabel" />
+
   <java-symbol type="layout" name="time_picker_holo" />
   <java-symbol type="layout" name="time_header_label" />
+  <java-symbol type="layout" name="year_label_text_view" />
+  <java-symbol type="layout" name="date_picker_holo" />
+
   <java-symbol type="id" name="time_header" />
   <java-symbol type="id" name="hours" />
   <java-symbol type="id" name="minutes" />
@@ -1864,6 +1878,15 @@
   <java-symbol type="id" name="separator" />
   <java-symbol type="id" name="layout_buttons" />
   <java-symbol type="id" name="done_button" />
+  <java-symbol type="id" name="date_picker_header" />
+  <java-symbol type="id" name="date_picker_month_and_day_layout" />
+  <java-symbol type="id" name="day_picker_selector_layout" />
+  <java-symbol type="id" name="date_picker_month" />
+  <java-symbol type="id" name="date_picker_day" />
+  <java-symbol type="id" name="date_picker_year" />
+  <java-symbol type="id" name="animator" />
+  <java-symbol type="id" name="done" />
+
   <java-symbol type="string" name="done_label" />
   <java-symbol type="string" name="hour_picker_description" />
   <java-symbol type="string" name="minute_picker_description" />
@@ -1885,7 +1908,42 @@
   <java-symbol type="string" name="timepicker_numbers_radius_multiplier_normal" />
   <java-symbol type="string" name="timepicker_transition_mid_radius_multiplier" />
   <java-symbol type="string" name="timepicker_transition_end_radius_multiplier" />
+
+  <java-symbol type="string" name="item_is_selected" />
+  <java-symbol type="string" name="day_of_week_label_typeface" />
+  <java-symbol type="string" name="select_day" />
+  <java-symbol type="string" name="day_picker_description" />
+  <java-symbol type="string" name="select_year" />
+  <java-symbol type="string" name="year_picker_description" />
+
+  <java-symbol type="dimen" name="datepicker_day_number_size" />
+  <java-symbol type="dimen" name="datepicker_month_label_size" />
+  <java-symbol type="dimen" name="datepicker_month_day_label_text_size" />
+  <java-symbol type="dimen" name="datepicker_month_list_item_header_height" />
+  <java-symbol type="dimen" name="datepicker_day_number_select_circle_radius" />
+  <java-symbol type="dimen" name="datepicker_view_animator_height" />
+  <java-symbol type="dimen" name="datepicker_year_label_height" />
+  <java-symbol type="dimen" name="datepicker_year_picker_padding_top" />
+
+  <java-symbol type="color" name="timepicker_default_text_color_holo_light" />
+  <java-symbol type="color" name="timepicker_default_disabled_color_holo_light" />
+  <java-symbol type="color" name="timepicker_default_ampm_unselected_background_color_holo_light" />
+  <java-symbol type="color" name="timepicker_default_ampm_selected_background_color_holo_light" />
+
+  <java-symbol type="color" name="datepicker_default_normal_text_color_holo_light" />
+  <java-symbol type="color" name="datepicker_default_disabled_text_color_holo_light" />
+  <java-symbol type="color" name="datepicker_default_circle_background_color_holo_light" />
+  <java-symbol type="color" name="datepicker_default_header_dayofweek_background_color_holo_light" />
+  <java-symbol type="color" name="datepicker_default_header_selector_background_holo_light" />
+
+  <java-symbol type="color" name="datepicker_default_normal_text_color_material_light" />
+  <java-symbol type="color" name="datepicker_default_disabled_text_color_material_light" />
+  <java-symbol type="color" name="datepicker_default_circle_background_color_material_light" />
+  <java-symbol type="color" name="datepicker_default_header_dayofweek_background_color_material_light" />
+  <java-symbol type="color" name="datepicker_default_header_selector_background_material_light" />
+
   <java-symbol type="array" name="config_clockTickVibePattern" />
+  <java-symbol type="array" name="config_calendarDateVibePattern" />
 
   <!-- From various Material changes -->
   <java-symbol type="attr" name="toolbarStyle" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index a519c37..0438ed1 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -409,7 +409,34 @@
         <!-- DatePicker style -->
         <item name="datePickerStyle">@style/Widget.DatePicker</item>
 
-        <item name="fastScrollThumbDrawable">@drawable/scrollbar_handle_accelerated_anim2</item>
+        <!-- DatePicker Header day of week label background color -->
+        <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/darker_gray</item>
+
+        <!-- DatePicker Header day of week label text appearance -->
+        <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.DatePicker.DayOfWeekLabel</item>
+
+        <!-- DatePicker Header selector background color -->
+        <item name="datePickerHeaderSelectorBackgroundColor">@android:color/darker_gray</item>
+
+        <!-- DatePicker Header selector month label text appearance -->
+        <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.DatePicker.Selector.MonthLabel</item>
+
+        <!-- DatePicker Header selector day of month label text appearance -->
+        <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.DatePicker.Selector.DayOfMonthLabel</item>
+
+        <!-- DatePicker Header selector year label text appearance -->
+        <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.DatePicker.Selector.YearLabel</item>
+
+        <!-- DatePicker list year label text appearance -->
+        <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.DatePicker.List.YearLabel</item>
+
+        <!-- DatePicker list year label circle background color -->
+        <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/darker_gray</item>
+
+        <!-- DatePicker dialog theme -->
+        <item name="datePickerDialogTheme">@android:style/Theme.Dialog.DatePicker</item>
+
+        <item name="fastScrollThumbDrawable">@android:drawable/scrollbar_handle_accelerated_anim2</item>
         <item name="fastScrollTrackDrawable">@null</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/menu_submenu_background</item>
         <item name="fastScrollPreviewBackgroundLeft">@drawable/menu_submenu_background</item>
@@ -731,6 +758,14 @@
         <item name="windowContentOverlay">@null</item>
     </style>
 
+    <!-- Default heme for the DatePicker dialog windows, which is used by the
+         {@link android.app.DatePickerDialog} class. -->
+    <style name="Theme.Dialog.DatePicker">
+        <item name="windowBackground">@android:color/transparent</item>
+        <item name="windowTitleStyle">@android:style/DialogWindowTitle</item>
+        <item name="windowContentOverlay">@null</item>
+    </style>
+
     <!-- Default dark theme for panel windows (on API level 10 and lower).  This removes all
          extraneous window decorations, so you basically have an empty rectangle in which
          to place your content.  It makes the window floating, with a transparent
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index ee2c7df..2febbef 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -207,7 +207,23 @@
         <!-- DatePicker style -->
         <item name="datePickerStyle">@style/Widget.DeviceDefault.DatePicker</item>
 
+        <!-- The DatePicker Header day of week label text appearance -->
+        <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.DayOfWeekLabel</item>
+
+        <!-- The DatePicker Header selector month label text appearance -->
+        <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.Selector.MonthLabel</item>
+
+        <!-- The DatePicker Header selector day of month label text appearance -->
+        <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.Selector.DayOfMonthLabel</item>
+
+        <!-- The DatePicker Header selector year label text appearance -->
+        <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.Selector.YearLabel</item>
+
+        <!-- DatePicker dialog theme -->
+        <item name="datePickerDialogTheme">@android:style/Theme.DeviceDefault.Dialog.DatePicker</item>
+
         <item name="mediaRouteButtonStyle">@style/Widget.DeviceDefault.MediaRouteButton</item>
+
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} with no action bar -->
@@ -466,7 +482,23 @@
         <!-- DatePicker style -->
         <item name="datePickerStyle">@style/Widget.DeviceDefault.Light.DatePicker</item>
 
+        <!-- The DatePicker Header day of week label text appearance -->
+        <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.DayOfWeekLabel</item>
+
+        <!-- The DatePicker Header selector month label text appearance -->
+        <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.Selector.MonthLabel</item>
+
+        <!-- The DatePicker Header selector day of month label text appearance -->
+        <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.Selector.DayOfMonthLabel</item>
+
+        <!-- The DatePicker Header selector year label text appearance -->
+        <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.Selector.YearLabel</item>
+
+        <!-- DatePicker dialog theme -->
+        <item name="datePickerDialogTheme">@android:style/Theme.DeviceDefault.Light.Dialog.DatePicker</item>
+
         <item name="mediaRouteButtonStyle">@style/Widget.DeviceDefault.Light.MediaRouteButton</item>
+
     </style>
 
     <!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index 76bfc4b..dda42c1 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -389,6 +389,33 @@
         <!-- DatePicker style -->
         <item name="datePickerStyle">@style/Widget.Holo.DatePicker</item>
 
+        <!-- DatePicker Header day of week label background color -->
+        <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_holo_dark</item>
+
+        <!-- DatePicker Header day of week label text appearance -->
+        <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.DayOfWeekLabel</item>
+
+        <!-- DatePicker Header selector background color -->
+        <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_holo_dark</item>
+
+        <!-- DatePicker Header selector month label text appearance -->
+        <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.Selector.MonthLabel</item>
+
+        <!-- DatePicker Header selector day of month label text appearance -->
+        <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.Selector.DayOfMonthLabel</item>
+
+        <!-- DatePicker Header selector year label text appearance -->
+        <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.Selector.YearLabel</item>
+
+        <!-- DatePicker list year label text appearance -->
+        <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.List.YearLabel</item>
+
+        <!-- DatePicker list year label circle background color -->
+        <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_holo_dark</item>
+
+        <!-- DatePicker dialog theme -->
+        <item name="datePickerDialogTheme">@android:style/Theme.Holo.Dialog.DatePicker</item>
+
         <item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_holo</item>
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item>
@@ -728,6 +755,33 @@
         <!-- DatePicker style -->
         <item name="datePickerStyle">@style/Widget.Holo.Light.DatePicker</item>
 
+        <!-- DatePicker Header day of week label background color -->
+        <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_holo_light</item>
+
+        <!-- DatePicker Header day of week label text appearance -->
+        <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.DayOfWeekLabel</item>
+
+        <!-- DatePicker Header selector background color -->
+        <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_holo_light</item>
+
+        <!-- DatePicker Header selector month label text appearance -->
+        <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.Selector.MonthLabel</item>
+
+        <!-- DatePicker Header selector day of month label text appearance -->
+        <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.Selector.DayOfMonthLabel</item>
+
+        <!-- DatePicker Header selector year label text appearance -->
+        <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.Selector.YearLabel</item>
+
+        <!-- DatePicker list year label text appearance -->
+        <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.List.YearLabel</item>
+
+        <!-- DatePicker list year label circle background color -->
+        <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_holo_light</item>
+
+        <!-- DatePicker dialog theme -->
+        <item name="datePickerDialogTheme">@android:style/Theme.Holo.Light.Dialog.DatePicker</item>
+
         <item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_holo</item>
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
@@ -944,6 +998,14 @@
          {@link android.app.TimePickerDialog} class. -->
     <style name="Theme.Holo.Dialog.TimePicker" parent="Theme.Holo.Dialog.Alert" />
 
+    <!-- Holo theme for the DatePicker dialog windows, which is used by the
+            {@link android.app.DatePickerDialog} class. -->
+    <style name="Theme.Holo.Dialog.DatePicker">
+        <item name="windowBackground">@color/transparent</item>
+        <item name="windowTitleStyle">@style/DialogWindowTitle.Holo</item>
+        <item name="windowContentOverlay">@null</item>
+    </style>
+
     <!-- 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). -->
@@ -1059,6 +1121,14 @@
          {@link android.app.TimePickerDialog} class. -->
     <style name="Theme.Holo.Light.Dialog.TimePicker" parent="Theme.Holo.Light.Dialog.Alert" />
 
+    <!-- Holo Light theme for the DatePicker dialog windows, which is used by the
+           {@link android.app.DatePickerDialog} class. -->
+    <style name="Theme.Holo.Light.Dialog.DatePicker">
+        <item name="windowBackground">@android:color/transparent</item>
+        <item name="windowTitleStyle">@android:style/DialogWindowTitle.Holo.Light</item>
+        <item name="windowContentOverlay">@null</item>
+    </style>
+
     <!-- Theme for a presentation window on a secondary display. -->
     <style name="Theme.Holo.Light.Dialog.Presentation" parent="Theme.Holo.Light.NoActionBar.Fullscreen" />
 
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index 6f6385b..534e323 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -62,4 +62,16 @@
         <item name="windowCloseOnTouchOutside">false</item>
         <item name="alertDialogStyle">@style/AlertDialog.Leanback</item>
     </style>
+
+    <!-- Setup and form wizard themes @hide @SystemApi-->
+    <style name="Theme.Leanback.FormWizard" parent="Theme.Material.NoActionBar">
+        <item name="windowBackground">@drawable/background_leanback_setup</item>
+        <item name="colorBackgroundCacheHint">@null</item>
+        <item name="windowShowWallpaper">false</item>
+        <item name="textAppearanceSmall">@style/TextAppearance.Leanback.FormWizard.Small</item>
+        <item name="textAppearanceMedium">@style/TextAppearance.Leanback.FormWizard.Medium</item>
+        <item name="textAppearanceLarge">@style/TextAppearance.Leanback.FormWizard.Large</item>
+        <item name="textAppearanceListItem">@style/TextAppearance.Leanback.FormWizard.ListItem</item>
+        <item name="textAppearance">@style/TextAppearance.Leanback.FormWizard</item>
+    </style>
 </resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index efc92d9..bdaeb45 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -367,6 +367,33 @@
         <!-- DatePicker style -->
         <item name="datePickerStyle">@style/Widget.Material.DatePicker</item>
 
+        <!-- DatePicker Header day of week label background color -->
+        <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_material_dark</item>
+
+        <!-- DatePicker Header day of week label text appearance -->
+        <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Material.DatePicker.DayOfWeekLabel</item>
+
+        <!-- DatePicker Header selector background color -->
+        <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_material_dark</item>
+
+        <!-- DatePicker Header selector month label text appearance -->
+        <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Material.DatePicker.Selector.MonthLabel</item>
+
+        <!-- DatePicker Header selector day of month label text appearance -->
+        <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Material.DatePicker.Selector.DayOfMonthLabel</item>
+
+        <!-- DatePicker Header selector year label text appearance -->
+        <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Material.DatePicker.Selector.YearLabel</item>
+
+        <!-- DatePicker list year label text appearance -->
+        <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Material.DatePicker.List.YearLabel</item>
+
+        <!-- DatePicker list year label circle background color -->
+        <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_material_dark</item>
+
+        <!-- DatePicker dialog theme -->
+        <item name="datePickerDialogTheme">@android:style/Theme.Material.Dialog.DatePicker</item>
+
         <!-- TODO: This belongs in a FastScroll style -->
         <item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_material</item>
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
@@ -715,6 +742,33 @@
         <!-- DatePicker style -->
         <item name="datePickerStyle">@style/Widget.Material.Light.DatePicker</item>
 
+        <!-- DatePicker Header day of week label background color -->
+        <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_material_light</item>
+
+        <!-- DatePicker Header day of week label text appearance -->
+        <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.DayOfWeekLabel</item>
+
+        <!-- DatePicker Header selector background color -->
+        <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_material_light</item>
+
+        <!-- DatePicker Header selector month label text appearance -->
+        <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.Selector.MonthLabel</item>
+
+        <!-- DatePicker Header selector day of month label text appearance -->
+        <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.Selector.DayOfMonthLabel</item>
+
+        <!-- DatePicker Header selector year label text appearance -->
+        <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.Selector.YearLabel</item>
+
+        <!-- DatePicker list year label text appearance -->
+        <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.List.YearLabel</item>
+
+        <!-- DatePicker list year label circle background color -->
+        <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_material_light</item>
+
+        <!-- DatePicker dialog theme -->
+        <item name="datePickerDialogTheme">@android:style/Theme.Material.Light.Dialog.DatePicker</item>
+
         <item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_material</item>
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
@@ -1110,6 +1164,16 @@
          {@link android.app.TimePickerDialog} class. -->
     <style name="Theme.Material.Dialog.TimePicker" parent="Theme.Material.Dialog.BaseTimePicker"/>
 
+    <style name="Theme.Material.Dialog.BaseDatePicker">
+        <item name="windowBackground">@color/transparent</item>
+        <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
+        <item name="windowContentOverlay">@null</item>
+    </style>
+
+    <!-- Material theme for the DatePicker dialog windows, which is used by the
+         {@link android.app.DatePickerDialog} class. -->
+    <style name="Theme.Material.Dialog.DatePicker" parent="Theme.Material.Dialog.BaseDatePicker"/>
+
     <!-- 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). -->
@@ -1222,12 +1286,23 @@
         <item name="windowBackground">@color/transparent</item>
         <item name="windowElevation">0dp</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
+        <item name="windowContentOverlay">@null</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"/>
 
+    <style name="Theme.Material.Light.Dialog.BaseDatePicker">
+        <item name="windowBackground">@color/transparent</item>
+        <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
+        <item name="windowContentOverlay">@null</item>
+    </style>
+
+    <!-- Material Light theme for the DatePicker dialog windows, which is used by the
+         {@link android.app.DatePickerDialog} class. -->
+    <style name="Theme.Material.Light.Dialog.DatePicker" parent="Theme.Material.Light.Dialog.BaseDatePicker"/>
+
     <!-- 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/docs/html/distribute/googleplay/edu/about.jd b/docs/html/distribute/googleplay/edu/about.jd
index 60d9402..469b899 100644
--- a/docs/html/distribute/googleplay/edu/about.jd
+++ b/docs/html/distribute/googleplay/edu/about.jd
@@ -21,16 +21,6 @@
   directly to classrooms and schools.
 </p>
 
-
-<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,"
-  data-maxResults="1"></div>
-
-
-<!-- <div class="center-img"><img src="{@docRoot}images/gp-edu-hero14.jpg" class="" /></div> -->
-
 <p>
   If you have an educational app, include it in Google Play for Education.
   Google Play for Education can help your innovative educational apps gain
diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd
index 136611c..067a15f 100644
--- a/docs/html/distribute/googleplay/edu/start.jd
+++ b/docs/html/distribute/googleplay/edu/start.jd
@@ -1,4 +1,4 @@
-page.title=Publish Android Apps for Education
+page.title=Publish Apps
 page.image=/distribute/images/play-education.jpg
 meta.tags="education", "guidelines", "quality"
 page.tags="education", "addendum"
diff --git a/docs/html/distribute/googleplay/edu/videos.jd b/docs/html/distribute/googleplay/edu/videos.jd
new file mode 100644
index 0000000..ca4da7a
--- /dev/null
+++ b/docs/html/distribute/googleplay/edu/videos.jd
@@ -0,0 +1,30 @@
+page.title=Video Resources
+page.image=http://i1.ytimg.com/vi/vzvpcEffvaE/maxresdefault.jpg
+meta.tags="education"
+page.tags="education"
+page.metaDescription=Watch video resources from Google and successful educational developers.
+
+@jd:body
+
+<p>
+With Google Play for Education, bringing your app to the classroom has never been easier. However,
+you may want to familiarize yourself with some best practices before diving in.
+To help you visualize what a great education app might look like, what Google Play for Education is all
+about, and how to achieve success with the platform, here is a selection of recent videos from
+Google and successful educational developers.
+</p>
+
+<h2>Developer Stories</h2>
+<div style="margin-top:20px" class="resource-widget resource-flow-layout wrap col-13
+   no-section" data-query="collection:distribute/edu/videos/stories" data-resourcestyle="card"
+   data-maxresults="6" data-cardsizes="18x12x2"></div>
+
+<h2>Best Practices</h2>
+<div style="margin-top:20px" class="resource-widget resource-flow-layout wrap col-13
+   no-section" data-query="collection:distribute/edu/videos/bestpractices" data-resourcestyle="card"
+   data-maxresults="6" data-cardsizes="18x6, 6x6, 6x6, 6x6"></div>
+
+<h2>Teacher/Classroom Experience</h2>
+<div style="margin-top:20px" class="resource-widget resource-flow-layout wrap col-13
+   no-section" data-query="collection:distribute/edu/videos/experience" data-resourcestyle="card"
+   data-maxresults="6" data-cardsizes="18x12x2"></div>
\ No newline at end of file
diff --git a/docs/html/distribute/googleplay/googleplay_toc.cs b/docs/html/distribute/googleplay/googleplay_toc.cs
index 36e424a..45464c7 100644
--- a/docs/html/distribute/googleplay/googleplay_toc.cs
+++ b/docs/html/distribute/googleplay/googleplay_toc.cs
@@ -18,24 +18,24 @@
     </div>
   </li>
   <li class="nav-section">
-    <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/googleplay/edu/about.html">
+    <div class="nav-section-header" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/googleplay/edu/about.html">
           <span class="en">Google Play for 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/start.html">
-          <span class="en">Publish Android Apps for Education</span>
-        </a>
-    </div>
+    <ul>
+      <li><a href="<?cs var:toroot?>distribute/googleplay/edu/start.html">
+          <span class="en">Publish Apps</span>
+        </a></li>
+      <li><a href="<?cs var:toroot?>distribute/googleplay/edu/videos.html">
+          <span class="en">Video Resources</span>
+        </a></li>
+    </ul>
   </li>
 </ul>
 
-
 <script type="text/javascript">
 <!--
     buildToggleLists();
     changeNavLang(getLangPref());
 //-->
-</script>
-
+</script>
\ No newline at end of file
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 4389e3d..90d8d4c 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -535,6 +535,8 @@
 import org.jivesoftware.smack.ConnectionListener;
 import org.jivesoftware.smack.PacketInterceptor;
 import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
 import org.jivesoftware.smack.XMPPConnection;
 import org.jivesoftware.smack.XMPPException;
 import org.jivesoftware.smack.filter.PacketTypeFilter;
@@ -544,352 +546,378 @@
 import org.jivesoftware.smack.packet.PacketExtension;
 import org.jivesoftware.smack.provider.PacketExtensionProvider;
 import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.tcp.XMPPTCPConnection;
 import org.jivesoftware.smack.util.StringUtils;
 import org.json.simple.JSONValue;
 import org.json.simple.parser.ParseException;
 import org.xmlpull.v1.XmlPullParser;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Random;
+import java.util.UUID;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.net.ssl.SSLSocketFactory;
+
 /**
- * Sample Smack implementation of a client for GCM Cloud Connection Server.
+ * Sample Smack implementation of a client for GCM Cloud Connection Server. This
+ * code can be run as a standalone CCS client.
  *
  * &lt;p&gt;For illustration purposes only.
  */
 public class SmackCcsClient {
 
-  Logger logger = Logger.getLogger(&quot;SmackCcsClient&quot;);
+    private static final Logger logger = Logger.getLogger(&quot;SmackCcsClient&quot;);
 
-  public static final String GCM_SERVER = &quot;gcm.googleapis.com&quot;;
-  public static final int GCM_PORT = 5235;
+    private static final String GCM_SERVER = &quot;gcm.googleapis.com&quot;;
+    private static final int GCM_PORT = 5235;
 
-  public static final String GCM_ELEMENT_NAME = &quot;gcm&quot;;
-  public static final String GCM_NAMESPACE = &quot;google:mobile:data&quot;;
+    private static final String GCM_ELEMENT_NAME = &quot;gcm&quot;;
+    private static final String GCM_NAMESPACE = &quot;google:mobile:data&quot;;
 
-  static Random random = new Random();
-  XMPPConnection connection;
-  ConnectionConfiguration config;
+    static {
 
-  /**
-   * XMPP Packet Extension for GCM Cloud Connection Server.
-   */
-  class GcmPacketExtension extends DefaultPacketExtension {
-    String json;
-
-    public GcmPacketExtension(String json) {
-      super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
-      this.json = json;
+        ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
+            new PacketExtensionProvider() {
+                &#64;Override
+                public PacketExtension parseExtension(XmlPullParser parser) throws
+                        Exception {
+                    String json = parser.nextText();
+                    return new GcmPacketExtension(json);
+                }
+            });
     }
 
-    public String getJson() {
-      return json;
+    private XMPPConnection connection;
+
+    /**
+     * Indicates whether the connection is in draining state, which means that it
+     * will not accept any new downstream messages.
+     */
+    protected volatile boolean connectionDraining = false;
+
+    /**
+     * Sends a downstream message to GCM.
+     *
+     * &#64;return true if the message has been successfully sent.
+     */
+    public boolean sendDownstreamMessage(String jsonRequest) throws
+            NotConnectedException {
+        if (!connectionDraining) {
+            send(jsonRequest);
+            return true;
+        }
+        logger.info(&quot;Dropping downstream message since the connection is draining&quot;);
+        return false;
     }
 
-    &#64;Override
-    public String toXML() {
-      return String.format(&quot;&lt;%s xmlns=\&quot;%s\&quot;&gt;%s&lt;/%s&gt;&quot;, GCM_ELEMENT_NAME,
-          GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
+    /**
+     * Returns a random message id to uniquely identify a message.
+     *
+     * &lt;p&gt;Note: This is generated by a pseudo random number generator for
+     * illustration purpose, and is not guaranteed to be unique.
+     */
+    public String nextMessageId() {
+        return &quot;m-&quot; + UUID.randomUUID().toString();
     }
 
-    &#64;SuppressWarnings(&quot;unused&quot;)
-    public Packet toPacket() {
-      return new Message() {
-        // Must override toXML() because it includes a &lt;body&gt;
+    /**
+     * Sends a packet with contents provided.
+     */
+    protected void send(String jsonRequest) throws NotConnectedException {
+        Packet request = new GcmPacketExtension(jsonRequest).toPacket();
+        connection.sendPacket(request);
+    }
+
+    /**
+     * Handles an upstream data message from a device application.
+     *
+     * &lt;p&gt;This sample echo server sends an echo message back to the device.
+     * Subclasses should override this method to properly process upstream messages.
+     */
+    protected void handleUpstreamMessage(Map&lt;String, Object&gt; jsonObject) {
+        // PackageName of the application that sent this message.
+        String category = (String) jsonObject.get(&quot;category&quot;);
+        String from = (String) jsonObject.get(&quot;from&quot;);
+        &#64;SuppressWarnings(&quot;unchecked&quot;)
+        Map&lt;String, String&gt; payload = (Map&lt;String, String&gt;) jsonObject.get(&quot;data&quot;);
+        payload.put(&quot;ECHO&quot;, &quot;Application: &quot; + category);
+
+        // Send an ECHO response back
+        String echo = createJsonMessage(from, nextMessageId(), payload,
+                &quot;echo:CollapseKey&quot;, null, false);
+
+        try {
+            sendDownstreamMessage(echo);
+        } catch (NotConnectedException e) {
+            logger.log(Level.WARNING, &quot;Not connected anymore, echo message is
+                    not sent&quot;, e);
+        }
+    }
+
+    /**
+     * Handles an ACK.
+     *
+     * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
+     * properly handle ACKs.
+     */
+    protected void handleAckReceipt(Map&lt;String, Object&gt; jsonObject) {
+        String messageId = (String) jsonObject.get(&quot;message_id&quot;);
+        String from = (String) jsonObject.get(&quot;from&quot;);
+        logger.log(Level.INFO, &quot;handleAckReceipt() from: &quot; + from + &quot;,
+                messageId: &quot; + messageId);
+    }
+
+    /**
+     * Handles a NACK.
+     *
+     * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
+     * properly handle NACKs.
+     */
+    protected void handleNackReceipt(Map&lt;String, Object&gt; jsonObject) {
+        String messageId = (String) jsonObject.get(&quot;message_id&quot;);
+        String from = (String) jsonObject.get(&quot;from&quot;);
+        logger.log(Level.INFO, &quot;handleNackReceipt() from: &quot; + from + &quot;,
+                messageId: &quot; + messageId);
+    }
+
+    protected void handleControlMessage(Map&lt;String, Object&gt; jsonObject) {
+        logger.log(Level.INFO, &quot;handleControlMessage(): &quot; + jsonObject);
+        String controlType = (String) jsonObject.get(&quot;control_type&quot;);
+        if (&quot;CONNECTION_DRAINING&quot;.equals(controlType)) {
+            connectionDraining = true;
+        } else {
+            logger.log(Level.INFO, &quot;Unrecognized control type: %s. This could
+                    happen if new features are &quot; + &quot;added to the CCS protocol.&quot;,
+                    controlType);
+        }
+    }
+
+    /**
+     * Creates a JSON encoded GCM message.
+     *
+     * &#64;param to RegistrationId of the target device (Required).
+     * &#64;param messageId Unique messageId for which CCS will send an
+     *         &quot;ack/nack&quot; (Required).
+     * &#64;param payload Message content intended for the application. (Optional).
+     * &#64;param collapseKey GCM collapse_key parameter (Optional).
+     * &#64;param timeToLive GCM time_to_live parameter (Optional).
+     * &#64;param delayWhileIdle GCM delay_while_idle parameter (Optional).
+     * &#64;return JSON encoded GCM message.
+     */
+    public static String createJsonMessage(String to, String messageId,
+            Map&lt;String, String&gt; payload, String collapseKey, Long timeToLive,
+            Boolean delayWhileIdle) {
+        Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
+        message.put(&quot;to&quot;, to);
+        if (collapseKey != null) {
+            message.put(&quot;collapse_key&quot;, collapseKey);
+        }
+        if (timeToLive != null) {
+            message.put(&quot;time_to_live&quot;, timeToLive);
+        }
+        if (delayWhileIdle != null &amp;&amp; delayWhileIdle) {
+            message.put(&quot;delay_while_idle&quot;, true);
+        }
+      message.put(&quot;message_id&quot;, messageId);
+      message.put(&quot;data&quot;, payload);
+      return JSONValue.toJSONString(message);
+    }
+
+    /**
+     * Creates a JSON encoded ACK message for an upstream message received
+     * from an application.
+     *
+     * &#64;param to RegistrationId of the device who sent the upstream message.
+     * &#64;param messageId messageId of the upstream message to be acknowledged to CCS.
+     * &#64;return JSON encoded ack.
+     */
+        protected static String createJsonAck(String to, String messageId) {
+        Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
+        message.put(&quot;message_type&quot;, &quot;ack&quot;);
+        message.put(&quot;to&quot;, to);
+        message.put(&quot;message_id&quot;, messageId);
+        return JSONValue.toJSONString(message);
+    }
+
+    /**
+     * Connects to GCM Cloud Connection Server using the supplied credentials.
+     *
+     * &#64;param senderId Your GCM project number
+     * &#64;param apiKey API Key of your project
+     */
+    public void connect(long senderId, String apiKey)
+            throws XMPPException, IOException, SmackException {
+        ConnectionConfiguration config =
+                new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
+        config.setSecurityMode(SecurityMode.enabled);
+        config.setReconnectionAllowed(true);
+        config.setRosterLoadedAtLogin(false);
+        config.setSendPresence(false);
+        config.setSocketFactory(SSLSocketFactory.getDefault());
+
+        connection = new XMPPTCPConnection(config);
+        connection.connect();
+
+        connection.addConnectionListener(new LoggingConnectionListener());
+
+        // Handle incoming packets
+        connection.addPacketListener(new PacketListener() {
+
+            &#64;Override
+            public void processPacket(Packet packet) {
+                logger.log(Level.INFO, &quot;Received: &quot; + packet.toXML());
+                Message incomingMessage = (Message) packet;
+                GcmPacketExtension gcmPacket =
+                        (GcmPacketExtension) incomingMessage.
+                        getExtension(GCM_NAMESPACE);
+                String json = gcmPacket.getJson();
+                try {
+                    &#64;SuppressWarnings(&quot;unchecked&quot;)
+                    Map&lt;String, Object&gt; jsonObject =
+                            (Map&lt;String, Object&gt;) JSONValue.
+                            parseWithException(json);
+
+                    // present for &quot;ack&quot;/&quot;nack&quot;, null otherwise
+                    Object messageType = jsonObject.get(&quot;message_type&quot;);
+
+                    if (messageType == null) {
+                        // Normal upstream data message
+                        handleUpstreamMessage(jsonObject);
+
+                        // Send ACK to CCS
+                        String messageId = (String) jsonObject.get(&quot;message_id&quot;);
+                        String from = (String) jsonObject.get(&quot;from&quot;);
+                        String ack = createJsonAck(from, messageId);
+                        send(ack);
+                    } else if (&quot;ack&quot;.equals(messageType.toString())) {
+                          // Process Ack
+                          handleAckReceipt(jsonObject);
+                    } else if (&quot;nack&quot;.equals(messageType.toString())) {
+                          // Process Nack
+                          handleNackReceipt(jsonObject);
+                    } else if (&quot;control&quot;.equals(messageType.toString())) {
+                          // Process control message
+                          handleControlMessage(jsonObject);
+                    } else {
+                          logger.log(Level.WARNING,
+                                  &quot;Unrecognized message type (%s)&quot;,
+                                  messageType.toString());
+                    }
+                } catch (ParseException e) {
+                    logger.log(Level.SEVERE, &quot;Error parsing JSON &quot; + json, e);
+                } catch (Exception e) {
+                    logger.log(Level.SEVERE, &quot;Failed to process packet&quot;, e);
+                }
+            }
+        }, new PacketTypeFilter(Message.class));
+
+        // Log all outgoing packets
+        connection.addPacketInterceptor(new PacketInterceptor() {
+            &#64;Override
+                public void interceptPacket(Packet packet) {
+                    logger.log(Level.INFO, &quot;Sent: {0}&quot;, packet.toXML());
+                }
+            }, new PacketTypeFilter(Message.class));
+
+        connection.login(senderId + &quot;&#64;gcm.googleapis.com&quot;, apiKey);
+    }
+
+    public static void main(String[] args) throws Exception {
+        final long senderId = 1234567890L; // your GCM sender id
+        final String password = &quot;Your API key&quot;;
+
+        SmackCcsClient ccsClient = new SmackCcsClient();
+
+        ccsClient.connect(senderId, password);
+
+        // Send a sample hello downstream message to a device.
+        String toRegId = &quot;RegistrationIdOfTheTargetDevice&quot;;
+        String messageId = ccsClient.nextMessageId();
+        Map&lt;String, String&gt; payload = new HashMap&lt;String, String&gt;();
+        payload.put(&quot;Hello&quot;, &quot;World&quot;);
+        payload.put(&quot;CCS&quot;, &quot;Dummy Message&quot;);
+        payload.put(&quot;EmbeddedMessageId&quot;, messageId);
+        String collapseKey = &quot;sample&quot;;
+        Long timeToLive = 10000L;
+        String message = createJsonMessage(toRegId, messageId, payload,
+                collapseKey, timeToLive, true);
+
+        ccsClient.sendDownstreamMessage(message);
+    }
+
+    /**
+     * XMPP Packet Extension for GCM Cloud Connection Server.
+     */
+    private static final class GcmPacketExtension extends DefaultPacketExtension {
+
+        private final String json;
+
+        public GcmPacketExtension(String json) {
+            super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
+            this.json = json;
+        }
+
+        public String getJson() {
+            return json;
+        }
+
         &#64;Override
         public String toXML() {
-
-          StringBuilder buf = new StringBuilder();
-          buf.append(&quot;&lt;message&quot;);
-          if (getXmlns() != null) {
-            buf.append(&quot; xmlns=\&quot;&quot;).append(getXmlns()).append(&quot;\&quot;&quot;);
-          }
-          if (getLanguage() != null) {
-            buf.append(&quot; xml:lang=\&quot;&quot;).append(getLanguage()).append(&quot;\&quot;&quot;);
-          }
-          if (getPacketID() != null) {
-            buf.append(&quot; id=\&quot;&quot;).append(getPacketID()).append(&quot;\&quot;&quot;);
-          }
-          if (getTo() != null) {
-            buf.append(&quot; to=\&quot;&quot;).append(StringUtils.escapeForXML(getTo())).append(&quot;\&quot;&quot;);
-          }
-          if (getFrom() != null) {
-            buf.append(&quot; from=\&quot;&quot;).append(StringUtils.escapeForXML(getFrom())).append(&quot;\&quot;&quot;);
-          }
-          buf.append(&quot;&gt;&quot;);
-          buf.append(GcmPacketExtension.this.toXML());
-          buf.append(&quot;&lt;/message&gt;&quot;);
-          return buf.toString();
+            return String.format(&quot;&lt;%s xmlns=\&quot;%s\&quot;&gt;%s&lt;/%s&gt;&quot;,
+                    GCM_ELEMENT_NAME, GCM_NAMESPACE,
+                    StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
         }
-      };
-    }
-  }
 
-  public SmackCcsClient() {
-    // Add GcmPacketExtension
-    ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
-        GCM_NAMESPACE, new PacketExtensionProvider() {
-
-      &#64;Override
-      public PacketExtension parseExtension(XmlPullParser parser)
-          throws Exception {
-        String json = parser.nextText();
-        GcmPacketExtension packet = new GcmPacketExtension(json);
-        return packet;
-      }
-    });
-  }
-
-  /**
-   * Returns a random message id to uniquely identify a message.
-   *
-   * &lt;p&gt;Note:
-   * This is generated by a pseudo random number generator for illustration purpose,
-   * and is not guaranteed to be unique.
-   *
-   */
-  public String getRandomMessageId() {
-    return &quot;m-&quot; + Long.toString(random.nextLong());
-  }
-
-  /**
-   * Sends a downstream GCM message.
-   */
-  public void send(String jsonRequest) {
-    Packet request = new GcmPacketExtension(jsonRequest).toPacket();
-    connection.sendPacket(request);
-  }
-
-  /**
-   * Handles an upstream data message from a device application.
-   *
-   * &lt;p&gt;This sample echo server sends an echo message back to the device.
-   * Subclasses should override this method to process an upstream message.
-   */
-  public void handleIncomingDataMessage(Map&lt;String, Object&gt; jsonObject) {
-    String from = jsonObject.get(&quot;from&quot;).toString();
-
-    // PackageName of the application that sent this message.
-    String category = jsonObject.get(&quot;category&quot;).toString();
-
-    // Use the packageName as the collapseKey in the echo packet
-    String collapseKey = &quot;echo:CollapseKey&quot;;
-    &#64;SuppressWarnings(&quot;unchecked&quot;)
-    Map&lt;String, String&gt; payload = (Map&lt;String, String&gt;) jsonObject.get(&quot;data&quot;);
-    payload.put(&quot;ECHO&quot;, &quot;Application: &quot; + category);
-
-    // Send an ECHO response back
-    String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false);
-    send(echo);
-  }
-
-  /**
-   * Handles an ACK.
-   *
-   * &lt;p&gt;By default, it only logs a {@code INFO} message, but subclasses could override it to
-   * properly handle ACKS.
-   */
-  public void handleAckReceipt(Map&lt;String, Object&gt; jsonObject) {
-    String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-    String from = jsonObject.get(&quot;from&quot;).toString();
-    logger.log(Level.INFO, &quot;handleAckReceipt() from: &quot; + from + &quot;, messageId: &quot; + messageId);
-  }
-
-  /**
-   * Handles a NACK.
-   *
-   * &lt;p&gt;By default, it only logs a {@code INFO} message, but subclasses could override it to
-   * properly handle NACKS.
-   */
-  public void handleNackReceipt(Map&lt;String, Object&gt; jsonObject) {
-    String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-    String from = jsonObject.get(&quot;from&quot;).toString();
-    logger.log(Level.INFO, &quot;handleNackReceipt() from: &quot; + from + &quot;, messageId: &quot; + messageId);
-  }
-
-  /**
-   * Creates a JSON encoded GCM message.
-   *
-   * &#64;param to RegistrationId of the target device (Required).
-   * &#64;param messageId Unique messageId for which CCS will send an &quot;ack/nack&quot; (Required).
-   * &#64;param payload Message content intended for the application. (Optional).
-   * &#64;param collapseKey GCM collapse_key parameter (Optional).
-   * &#64;param timeToLive GCM time_to_live parameter (Optional).
-   * &#64;param delayWhileIdle GCM delay_while_idle parameter (Optional).
-   * &#64;return JSON encoded GCM message.
-   */
-  public static String createJsonMessage(String to, String messageId, Map&lt;String, String&gt; payload,
-      String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
-    Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
-    message.put(&quot;to&quot;, to);
-    if (collapseKey != null) {
-      message.put(&quot;collapse_key&quot;, collapseKey);
-    }
-    if (timeToLive != null) {
-      message.put(&quot;time_to_live&quot;, timeToLive);
-    }
-    if (delayWhileIdle != null &amp;&amp; delayWhileIdle) {
-      message.put(&quot;delay_while_idle&quot;, true);
-    }
-    message.put(&quot;message_id&quot;, messageId);
-    message.put(&quot;data&quot;, payload);
-    return JSONValue.toJSONString(message);
-  }
-
-  /**
-   * Creates a JSON encoded ACK message for an upstream message received from an application.
-   *
-   * &#64;param to RegistrationId of the device who sent the upstream message.
-   * &#64;param messageId messageId of the upstream message to be acknowledged to CCS.
-   * &#64;return JSON encoded ack.
-   */
-  public static String createJsonAck(String to, String messageId) {
-    Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
-    message.put(&quot;message_type&quot;, &quot;ack&quot;);
-    message.put(&quot;to&quot;, to);
-    message.put(&quot;message_id&quot;, messageId);
-    return JSONValue.toJSONString(message);
-  }
-
-  /**
-   * Connects to GCM Cloud Connection Server using the supplied credentials.
-   *
-   * &#64;param username GCM_SENDER_ID&#64;gcm.googleapis.com
-   * &#64;param password API Key
-   * &#64;throws XMPPException
-   */
-  public void connect(String username, String password) throws XMPPException {
-    config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
-    config.setSecurityMode(SecurityMode.enabled);
-    config.setReconnectionAllowed(true);
-    config.setRosterLoadedAtLogin(false);
-    config.setSendPresence(false);
-    config.setSocketFactory(SSLSocketFactory.getDefault());
-
-    // NOTE: Set to true to launch a window with information about packets sent and received
-    config.setDebuggerEnabled(true);
-
-    // -Dsmack.debugEnabled=true
-    XMPPConnection.DEBUG_ENABLED = true;
-
-    connection = new XMPPConnection(config);
-    connection.connect();
-
-    connection.addConnectionListener(new ConnectionListener() {
-
-      &#64;Override
-      public void reconnectionSuccessful() {
-        logger.info(&quot;Reconnecting..&quot;);
-      }
-
-      &#64;Override
-      public void reconnectionFailed(Exception e) {
-        logger.log(Level.INFO, &quot;Reconnection failed.. &quot;, e);
-      }
-
-      &#64;Override
-      public void reconnectingIn(int seconds) {
-        logger.log(Level.INFO, &quot;Reconnecting in %d secs&quot;, seconds);
-      }
-
-      &#64;Override
-      public void connectionClosedOnError(Exception e) {
-        logger.log(Level.INFO, &quot;Connection closed on error.&quot;);
-      }
-
-      &#64;Override
-      public void connectionClosed() {
-        logger.info(&quot;Connection closed.&quot;);
-      }
-    });
-
-    // Handle incoming packets
-    connection.addPacketListener(new PacketListener() {
-
-      &#64;Override
-      public void processPacket(Packet packet) {
-        logger.log(Level.INFO, &quot;Received: &quot; + packet.toXML());
-        Message incomingMessage = (Message) packet;
-        GcmPacketExtension gcmPacket =
-            (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
-        String json = gcmPacket.getJson();
-        try {
-          &#64;SuppressWarnings(&quot;unchecked&quot;)
-          Map&lt;String, Object&gt; jsonObject =
-              (Map&lt;String, Object&gt;) JSONValue.parseWithException(json);
-
-          // present for &quot;ack&quot;/&quot;nack&quot;, null otherwise
-          Object messageType = jsonObject.get(&quot;message_type&quot;);
-
-          if (messageType == null) {
-            // Normal upstream data message
-            handleIncomingDataMessage(jsonObject);
-
-            // Send ACK to CCS
-            String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-            String from = jsonObject.get(&quot;from&quot;).toString();
-            String ack = createJsonAck(from, messageId);
-            send(ack);
-          } else if (&quot;ack&quot;.equals(messageType.toString())) {
-            // Process Ack
-            handleAckReceipt(jsonObject);
-          } else if (&quot;nack&quot;.equals(messageType.toString())) {
-            // Process Nack
-            handleNackReceipt(jsonObject);
-          } else {
-            logger.log(Level.WARNING, &quot;Unrecognized message type (%s)&quot;,
-                messageType.toString());
-          }
-        } catch (ParseException e) {
-          logger.log(Level.SEVERE, &quot;Error parsing JSON &quot; + json, e);
-        } catch (Exception e) {
-          logger.log(Level.SEVERE, &quot;Couldn't send echo.&quot;, e);
+        public Packet toPacket() {
+            Message message = new Message();
+            message.addExtension(this);
+            return message;
         }
-      }
-    }, new PacketTypeFilter(Message.class));
-
-
-    // Log all outgoing packets
-    connection.addPacketInterceptor(new PacketInterceptor() {
-      &#64;Override
-      public void interceptPacket(Packet packet) {
-        logger.log(Level.INFO, &quot;Sent: {0}&quot;,  packet.toXML());
-      }
-    }, new PacketTypeFilter(Message.class));
-
-    connection.login(username, password);
-  }
-
-  public static void main(String [] args) {
-    final String userName = &quot;Your GCM Sender Id&quot; + &quot;&#64;gcm.googleapis.com&quot;;
-    final String password = &quot;API Key&quot;;
-
-    SmackCcsClient ccsClient = new SmackCcsClient();
-
-    try {
-      ccsClient.connect(userName, password);
-    } catch (XMPPException e) {
-      e.printStackTrace();
     }
 
-    // Send a sample hello downstream message to a device.
-    String toRegId = &quot;RegistrationIdOfTheTargetDevice&quot;;
-    String messageId = ccsClient.getRandomMessageId();
-    Map&lt;String, String&gt; payload = new HashMap&lt;String, String&gt;();
-    payload.put(&quot;Hello&quot;, &quot;World&quot;);
-    payload.put(&quot;CCS&quot;, &quot;Dummy Message&quot;);
-    payload.put(&quot;EmbeddedMessageId&quot;, messageId);
-    String collapseKey = &quot;sample&quot;;
-    Long timeToLive = 10000L;
-    Boolean delayWhileIdle = true;
-    ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
-        timeToLive, delayWhileIdle));
-  }
+    private static final class LoggingConnectionListener
+            implements ConnectionListener {
+
+        &#64;Override
+        public void connected(XMPPConnection xmppConnection) {
+            logger.info(&quot;Connected.&quot;);
+        }
+
+        &#64;Override
+        public void authenticated(XMPPConnection xmppConnection) {
+            logger.info(&quot;Authenticated.&quot;);
+        }
+
+        &#64;Override
+        public void reconnectionSuccessful() {
+            logger.info(&quot;Reconnecting..&quot;);
+        }
+
+        &#64;Override
+        public void reconnectionFailed(Exception e) {
+            logger.log(Level.INFO, &quot;Reconnection failed.. &quot;, e);
+        }
+
+        &#64;Override
+        public void reconnectingIn(int seconds) {
+            logger.log(Level.INFO, &quot;Reconnecting in %d secs&quot;, seconds);
+        }
+
+        &#64;Override
+        public void connectionClosedOnError(Exception e) {
+            logger.info(&quot;Connection closed on error.&quot;);
+        }
+
+        &#64;Override
+        public void connectionClosed() {
+            logger.info(&quot;Connection closed.&quot;);
+        }
+    }
 }</pre>
+
 <h3 id="python">Python sample</h3>
 
 <p>Here is an example of a CCS app server written in Python. This sample echo
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index cc2047e..ebd3694 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -129,7 +129,7 @@
 <ol>
   <li>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.</li>
 
-  <li>In your app project, reference Google Play services library project. See
+  <li>In your app project, reference the Google Play services library project. See
     <a href="{@docRoot}tools/projects/projects-cmdline.html#ReferencingLibraryProject">Referencing
     a Library Project on the Command Line</a> for more information on how to do this.
     <p class="note"><strong>Note:</strong>
diff --git a/docs/html/google/play/billing/billing_overview.jd b/docs/html/google/play/billing/billing_overview.jd
index 301d911..12f8c9a 100644
--- a/docs/html/google/play/billing/billing_overview.jd
+++ b/docs/html/google/play/billing/billing_overview.jd
@@ -38,6 +38,16 @@
 features that you need to understand in order to add In-app 
 Billing features into your application.</p>
 
+<p class="note"><b>Note</b>: Ensure that you comply with applicable laws in the countries where you
+distribute apps. For example, in EU countries, laws based on the
+<a href="http://ec.europa.eu/justice/consumer-marketing/unfair-trade/unfair-practices/">Unfair
+Commercial Practices Directive</a> prohibit direct exhortations to children to buy advertised
+products or to persuade their parents or other adults to buy advertised products for them.
+See the
+<a href="http://ec.europa.eu/consumers/enforcement/docs/common_position_on_online_games_en.pdf">position
+of the EU consumer protection authorities</a> for more information on this and other topics.
+</p>
+
 <h2 id="api">In-app Billing API</h2>
 <p>Your application accesses the In-app Billing service using an API that is 
 exposed by the Google Play app that is installed on the device. The Google Play 
diff --git a/docs/html/guide/topics/graphics/2d-graphics.jd b/docs/html/guide/topics/graphics/2d-graphics.jd
index d842cb9..4b5a121 100644
--- a/docs/html/guide/topics/graphics/2d-graphics.jd
+++ b/docs/html/guide/topics/graphics/2d-graphics.jd
@@ -32,7 +32,7 @@
   </div>
 </div>
 
-<p>The Android framework APIs provides a set 2D drawing APIs that allow you to render your own
+<p>The Android framework APIs provides a set of 2D-drawing APIs that allow you to render your own
 custom graphics onto a canvas or to modify existing Views to customize their look and feel.
 When drawing 2D graphics, you'll typically do so in one of two ways:</p>
 
@@ -515,4 +515,4 @@
 stretches to accommodate it.
 </p>
 
-<img src="{@docRoot}images/ninepatch_examples.png" alt=""/>
\ No newline at end of file
+<img src="{@docRoot}images/ninepatch_examples.png" alt=""/>
diff --git a/docs/html/guide/topics/ui/declaring-layout.jd b/docs/html/guide/topics/ui/declaring-layout.jd
index 616949b..0c72657 100644
--- a/docs/html/guide/topics/ui/declaring-layout.jd
+++ b/docs/html/guide/topics/ui/declaring-layout.jd
@@ -88,8 +88,8 @@
 <pre>
 &lt;?xml version="1.0" encoding="utf-8"?>
 &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="fill_parent" 
-              android:layout_height="fill_parent" 
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
               android:orientation="vertical" >
     &lt;TextView android:id="@+id/text"
               android:layout_width="wrap_content"
@@ -215,14 +215,14 @@
 <ul>
   <li><var>wrap_content</var> tells your view to size itself to the dimensions
 required by its content</li>
-  <li><var>fill_parent</var> (renamed <var>match_parent</var> in API Level 8)
+  <li><var>match_parent</var> (named <var>fill_parent</var> before API Level 8)
 tells your view to become as big as its parent view group will allow.</li>
 </ul>
 
 <p>In general, specifying a layout width and height using absolute units such as
 pixels is not recommended. Instead, using relative measurements such as
 density-independent pixel units (<var>dp</var>), <var>wrap_content</var>, or
-<var>fill_parent</var>, is a better approach, because it helps ensure that
+<var>match_parent</var>, is a better approach, because it helps ensure that
 your application will display properly across a variety of device screen sizes.
 The accepted measurement types are defined in the
 <a href="{@docRoot}guide/topics/resources/available-resources.html#dimension">
diff --git a/docs/html/guide/topics/ui/layout/grid.jd b/docs/html/guide/topics/ui/layout/grid.jd
index 52f453b..c2f1321 100644
--- a/docs/html/guide/topics/ui/layout/grid.jd
+++ b/docs/html/guide/topics/ui/layout/grid.jd
@@ -41,8 +41,8 @@
         <pre>
 &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
 &lt;TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:stretchColumns="1">
     &lt;TableRow>
         &lt;TextView
@@ -82,8 +82,8 @@
 <pre>
 &lt;?xml version="1.0" encoding="utf-8"?>
 &lt;TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:stretchColumns="1">
 
     &lt;TableRow>
diff --git a/docs/html/guide/topics/ui/layout/gridview.jd b/docs/html/guide/topics/ui/layout/gridview.jd
index b8d24b60..a4bf224 100644
--- a/docs/html/guide/topics/ui/layout/gridview.jd
+++ b/docs/html/guide/topics/ui/layout/gridview.jd
@@ -43,10 +43,10 @@
   <li>Open the <code>res/layout/main.xml</code> file and insert the following:
 <pre>
 &lt;?xml version="1.0" encoding="utf-8"?>
-&lt;GridView xmlns:android="http://schemas.android.com/apk/res/android" 
+&lt;GridView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/gridview"
-    android:layout_width="fill_parent" 
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:columnWidth="90dp"
     android:numColumns="auto_fit"
     android:verticalSpacing="10dp"
diff --git a/docs/html/guide/topics/ui/layout/linear.jd b/docs/html/guide/topics/ui/layout/linear.jd
index 902f22f..f4babfe0 100644
--- a/docs/html/guide/topics/ui/layout/linear.jd
+++ b/docs/html/guide/topics/ui/layout/linear.jd
@@ -82,21 +82,21 @@
 <pre>
 &lt;?xml version="1.0" encoding="utf-8"?>
 &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:paddingLeft="16dp"
     android:paddingRight="16dp"
     android:orientation="vertical" >
     &lt;EditText
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:hint="@string/to" />
     &lt;EditText
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:hint="@string/subject" />
     &lt;EditText
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
         android:gravity="top"
diff --git a/docs/html/guide/topics/ui/layout/relative.jd b/docs/html/guide/topics/ui/layout/relative.jd
index 4354d7f..ca5cb48 100644
--- a/docs/html/guide/topics/ui/layout/relative.jd
+++ b/docs/html/guide/topics/ui/layout/relative.jd
@@ -82,13 +82,13 @@
 <pre>
 &lt;?xml version="1.0" encoding="utf-8"?>
 &lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:paddingLeft="16dp"
     android:paddingRight="16dp" >
     &lt;EditText
         android:id="@+id/name"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:hint="@string/reminder" />
     &lt;Spinner
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index cd49ace..852db1f 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -7,12 +7,26 @@
       "sdk/installing/studio.html" 
     ]
   },
-  "index/devices": {
+  "distribute/edu/videos/stories": {
     "title": "",
     "resources": [
-      "wear/index.html",
-      "tv/index.html",
-      "auto/index.html"
+      "https://www.youtube.com/watch?v=Idu7VcTTXfk",
+      "https://www.youtube.com/watch?v=iokH4SAIfRw"
+    ]
+  },
+  "distribute/edu/videos/bestpractices": {
+    "title": "",
+    "resources": [
+      "https://www.youtube.com/watch?v=iulXz8QTD1g",
+      "https://www.youtube.com/watch?v=IKhU180eJMo",
+      "https://www.youtube.com/watch?v=_AZ6UcPz-_g",
+      "https://www.youtube.com/watch?v=Eh2adsAyTKc"
+    ]
+  },
+  "distribute/edu/videos/experience": {
+    "title": "",
+    "resources": [
+      "http://youtu.be/vzvpcEffvaE"
     ]
   },
   "launch/static": {
@@ -45,7 +59,7 @@
     "resources": [
       "distribute/googleplay/edu/about.html",
       "distribute/googleplay/edu/start.html",
-      "https://developers.google.com/edu/faq"
+      "distribute/googleplay/edu/videos.html"
     ]
   },
   "distribute/essentials": {
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index dccdf7e..a4302ef 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -15,6 +15,90 @@
 
 DISTRIBUTE_RESOURCES = DISTRIBUTE_RESOURCES.concat([
   {
+    "title":"Quizlet Developer Story",
+    "titleFriendly":"",
+    "summary":"Quizlet is an extremely popular online learning tool for students. See how they optimized for the classroom with Android and the power of Google Play for Education.",
+    "url":"https://www.youtube.com/watch?v=Idu7VcTTXfk",
+    "group":"",
+    "keywords": [],
+    "tags": [
+      "#gpfe",
+      "#googleplay"
+    ],
+    "image":"http://i1.ytimg.com/vi/Idu7VcTTXfk/maxresdefault.jpg",
+    "type":"video"
+  },
+  {
+    "title":"Whats New in Google Play",
+    "titleFriendly":"",
+    "summary":"Learn about the vision and philosophy behind Google Play for Education",
+    "url":"https://www.youtube.com/watch?v=IKhU180eJMo",
+    "group":"",
+    "keywords": [],
+    "tags": [
+      "#gpfe",
+      "#googleplay"
+    ],
+    "image":"http://i1.ytimg.com/vi/IKhU180eJMo/maxresdefault.jpg",
+    "type":"video"
+  },
+  {
+    "title":"ClassDojo Developer Story",
+    "titleFriendly":"",
+    "summary":"ClassDojo is a classroom tool that helps teachers improve behavior in their classrooms quickly and easily. See how they optimized for the classroom with Android and the power of Google Play for Education.",
+    "url":"https://www.youtube.com/watch?v=iokH4SAIfRw",
+    "group":"",
+    "keywords": [],
+    "tags": [
+      "#gpfe",
+      "#googleplay"
+    ],
+    "image":"http://i1.ytimg.com/vi/iokH4SAIfRw/maxresdefault.jpg",
+    "type":"video"
+  },
+  {
+    "title":"5 Tips for Succeeding",
+    "titleFriendly":"",
+    "summary":"See inspirational ways of using technology in the classroom.",
+    "url":"https://www.youtube.com/watch?v=Eh2adsAyTKc",
+    "group":"",
+    "keywords": [],
+    "tags": [
+      "#gpfe",
+      "#googleplay"
+    ],
+    "image":"http://i1.ytimg.com/vi/Eh2adsAyTKc/maxresdefault.jpg",
+    "type":"video"
+  },
+  {
+    "title":"Optimizing Apps for Education",
+    "titleFriendly":"",
+    "summary":"Learn how to optimize your app for teachers and students.",
+    "url":"https://www.youtube.com/watch?v=_AZ6UcPz-_g",
+    "group":"",
+    "keywords": [],
+    "tags": [
+      "#gpfe",
+      "#googleplay"
+    ],
+    "image":"http://i1.ytimg.com/vi/_AZ6UcPz-_g/maxresdefault.jpg",
+    "type":"video"
+  },
+  {
+    "title":"Ideas and Tools for Building Innovative Education Apps",
+    "titleFriendly":"",
+    "summary":"Are you hungry to build an awesome app for education but don't quite know where to start? Come hear about apps that teachers want, and the APIs you're going to need to build them! In particular, we'll talk about app ideas that combine APIs for Google Drive, Google Login, Android Single Task Mode and more to build transformative Educational apps that will delight educators and kids in and out of the classroom.",
+    "url":"https://www.youtube.com/watch?v=iulXz8QTD1g",
+    "group":"",
+    "keywords": [],
+    "tags": [
+      "#gpfe",
+      "#googleplay"
+    ],
+    "image":"http://i1.ytimg.com/vi/iulXz8QTD1g/maxresdefault.jpg",
+    "type":"video"
+  },
+  {
     "title":"Developer Registration",
     "titleFriendly":"",
     "summary":"Additional information about the registration process.",
@@ -699,10 +783,10 @@
     "url": "http://youtu.be/vzvpcEffvaE",
     "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 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.",
+    "title": "Introducing Tablets with Google Play for Education",
+    "summary": "Schools in Hillsborough, New Jersey were among the first to try out Nexus 7 tablets with Google Play for Education. See the difference it made for students, teachers, and administrators.",
     "keywords": [],
-    "type": "youtube",
+    "type": "video",
     "titleFriendly": ""
   },
   {
diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd
index 16bc444..cf594dc 100644
--- a/docs/html/preview/api-overview.jd
+++ b/docs/html/preview/api-overview.jd
@@ -571,7 +571,7 @@
 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()}
+your app, call {@code android.bluetooth.le.BluetoothAdvertiser.startAdvertising()}
 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>
diff --git a/docs/html/preview/material/animations.jd b/docs/html/preview/material/animations.jd
index b8d063b..353f0f2 100644
--- a/docs/html/preview/material/animations.jd
+++ b/docs/html/preview/material/animations.jd
@@ -130,7 +130,7 @@
 <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>
+<li><em>fade</em> - Moves views in or out of the scene.</li>
 </ul>
 
 <p>Any transition that extends the <code>android.transition.Visibility</code> class is supported
@@ -236,7 +236,7 @@
 
 <h3>Shared elements transitions</h3>
 
-<p>To make a screne transition animation between two activities that have a shared element:</p>
+<p>To make a screen transition animation between two activities that have a shared element:</p>
 
 <ol>
 <li>Enable window content transitions in your style.</li>
diff --git a/docs/html/preview/material/compatibility.jd b/docs/html/preview/material/compatibility.jd
index fb97112..b4d26a7 100644
--- a/docs/html/preview/material/compatibility.jd
+++ b/docs/html/preview/material/compatibility.jd
@@ -78,5 +78,5 @@
 <li>Path-based animations</li>
 </ul>
 
-<p>To preserve compatibility with earlier verisons of Android, check the system version at
+<p>To preserve compatibility with earlier versions 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/theme.jd b/docs/html/preview/material/theme.jd
index 740bf56..dceeb47 100644
--- a/docs/html/preview/material/theme.jd
+++ b/docs/html/preview/material/theme.jd
@@ -6,7 +6,7 @@
 <div id="qv">
 <h2>In this document</h2>
 <ol>
-  <li><a href="#colorpalette">Customize the Colot Palette</a></li>
+  <li><a href="#colorpalette">Customize the Color Palette</a></li>
   <li><a href="#statusbar">Customize the Status Bar</a></li>
   <li><a href="#inheritance">Theme Individual Views</a></li>
 </ol>
diff --git a/docs/html/preview/material/ui-widgets.jd b/docs/html/preview/material/ui-widgets.jd
index 31604d6..2266815 100644
--- a/docs/html/preview/material/ui-widgets.jd
+++ b/docs/html/preview/material/ui-widgets.jd
@@ -171,10 +171,10 @@
 <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>
+  <li>To set the corner radius in your layouts, use the <code>card_view: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>
+  <li>To set the background color of a card, use the <code>card_view:cardBackgroundColor</code>
 attribute.</li>
 </ul>
 
diff --git a/docs/html/preview/material/views-shadows.jd b/docs/html/preview/material/views-shadows.jd
index f7682f5..78c0062 100644
--- a/docs/html/preview/material/views-shadows.jd
+++ b/docs/html/preview/material/views-shadows.jd
@@ -73,7 +73,7 @@
 &lt;/shape>
 </pre>
 
-<p>Then this view and drawable cast the appropiate shadow.</p>
+<p>Then this view and drawable cast the appropriate 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>
diff --git a/docs/html/preview/notifications.jd b/docs/html/preview/notifications.jd
index 375fad9..44f46ea5 100644
--- a/docs/html/preview/notifications.jd
+++ b/docs/html/preview/notifications.jd
@@ -183,7 +183,7 @@
 <img src="{@docRoot}preview/images/notifications/ReplyAction.png" width="156px" height="156px"
   alt="" />
 
-  
+
 </div>
 
 
@@ -315,7 +315,7 @@
 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
+from what they are doing. This should not be taken lightly, so these levels should be  reserved
 for notifications that:</p>
 
 <ul>
@@ -498,7 +498,7 @@
 
 <h3 style="clear:both" 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
+<p>Users should always be in control of notifications. Allow the user to disable 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>
 
diff --git a/docs/html/preview/tv/adt-1/index.jd b/docs/html/preview/tv/adt-1/index.jd
index d83dd11..e09dcbf 100644
--- a/docs/html/preview/tv/adt-1/index.jd
+++ b/docs/html/preview/tv/adt-1/index.jd
@@ -160,7 +160,7 @@
 <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
+  Cast target. For more information 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>
diff --git a/docs/html/preview/tv/design/patterns.jd b/docs/html/preview/tv/design/patterns.jd
index cdba74c..48faee9 100644
--- a/docs/html/preview/tv/design/patterns.jd
+++ b/docs/html/preview/tv/design/patterns.jd
@@ -45,7 +45,7 @@
 <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>
+  An example and specifications for this icon are shown below:</p>
 
 <img src="{@docRoot}preview/tv/design/images/icon.png" alt="Recommendation icon examples" />
 
diff --git a/docs/html/preview/tv/games/index.jd b/docs/html/preview/tv/games/index.jd
index 763eada..61a26d2c 100644
--- a/docs/html/preview/tv/games/index.jd
+++ b/docs/html/preview/tv/games/index.jd
@@ -146,7 +146,7 @@
 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>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/ui/navigation.jd b/docs/html/preview/tv/ui/navigation.jd
index 92b34cf..684b743 100644
--- a/docs/html/preview/tv/ui/navigation.jd
+++ b/docs/html/preview/tv/ui/navigation.jd
@@ -104,7 +104,7 @@
 
 <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:
+following code example demonstrates how to indicate selection of a button object:
 </p>
 
 <pre>
diff --git a/docs/html/tools/publishing/preparing.jd b/docs/html/tools/publishing/preparing.jd
index 7192aa8..5265fce 100644
--- a/docs/html/tools/publishing/preparing.jd
+++ b/docs/html/tools/publishing/preparing.jd
@@ -191,6 +191,13 @@
 added to your code, such as {@link android.os.Debug#startMethodTracing()} and
 {@link android.os.Debug#stopMethodTracing()} method calls.</p>
 
+<p class="caution"><strong>Important:</strong> Ensure that you disable debugging for
+your app if using {@link android.webkit.WebView} to display paid for content or if using JavaScript
+interfaces, since debugging allows users to inject scripts and extract content using Chrome
+DevTools. To disable debugging, use the
+{@link android.webkit.WebView#setWebContentsDebuggingEnabled(boolean) WebView.setWebContentsDebuggingEnabled()}
+method.</p>
+
 <h4>Clean up your project directories</h4>
 
 <p>Clean up your project and make sure it conforms to the directory structure described in <a
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index 0ac1881..71b15d5 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -2,33 +2,59 @@
 page.template=sdk
 
 
-ndk.mac64_download=android-ndk-r9d-darwin-x86_64.tar.bz2
-ndk.mac64_bytes=400339614
-ndk.mac64_checksum=c914164b1231c574dbe40debef7048be
+ndk.mac64_download=android-ndk32-r10-darwin-x86_64.tar.bz2
+ndk.mac64_bytes=411610468
+ndk.mac64_checksum=3ce1fa3dbe7a188f5d2640fd2f7ca944
 
-ndk.mac32_download=android-ndk-r9d-darwin-x86.tar.bz2
-ndk.mac32_bytes=393866116
-ndk.mac32_checksum=ee6544bd8093c79ea08c2e3a6ffe3573
+ndk.mac32_download=android-ndk32-r10-darwin-x86.tar.bz2
+ndk.mac32_bytes=404768263
+ndk.mac32_checksum=1824eec1f6749b6cb7bb306a3b924c33
 
-ndk.linux64_download=android-ndk-r9d-linux-x86_64.tar.bz2
-ndk.linux64_bytes=412879983
-ndk.linux64_checksum=c7c775ab3342965408d20fd18e71aa45
+ndk.linux64_download=android-ndk32-r10-linux-x86_64.tar.bz2
+ndk.linux64_bytes=420671390
+ndk.linux64_checksum=e3ff629d212a8106a43415862fa39baf
 
-ndk.linux32_download=android-ndk-r9d-linux-x86.tar.bz2
-ndk.linux32_bytes=405218267
-ndk.linux32_checksum=6c1d7d99f55f0c17ecbcf81ba0eb201f
+ndk.linux32_download=android-ndk32-r10-linux-x86.tar.bz2
+ndk.linux32_bytes=420078216
+ndk.linux32_checksum=8d9a5faa6e77b43bfae0f169079b21c4
 
-ndk.win64_download=android-ndk-r9d-windows-x86_64.zip
-ndk.win64_bytes=520997454
-ndk.win64_checksum=8cd244fc799d0e6e59d65a59a8692588
+ndk.win64_download=android-ndk32-r10-windows-x86_64.zip
+ndk.win64_bytes=529850429
+ndk.win64_checksum=b11f9239344f7c377ed5b627f0fb236e
 
-ndk.win32_download=android-ndk-r9d-windows-x86.zip
-ndk.win32_bytes=491440074
-ndk.win32_checksum=b16516b611841a075685a10c59d6d7a2
+ndk.win32_download=android-ndk32-r10-windows-x86.zip
+ndk.win32_bytes=500135685
+ndk.win32_checksum=0a3c01147abba945cc4ef5837519ec97
 
-ndk.debug_info_download=android-ndk-r9d-cxx-stl-libs-with-debug-info.zip
-ndk.debug_info_bytes=104947363
-ndk.debug_info_checksum=906c8d88e0f02295c3bfe6b8e98a1a35
+
+
+ndk.mac64_64_download=android-ndk64-r10-darwin-x86_64.tar.bz2
+ndk.mac64_64_bytes=327740247
+ndk.mac64_64_checksum=72561b27acc6192a2e81b345ea128a20
+
+ndk.mac32_64_download=android-ndk64-r10-darwin-x86.tar.bz2
+ndk.mac32_64_bytes=323736411
+ndk.mac32_64_checksum=5bbaf9d8051ba5d2c0fff74cfd87c374
+
+ndk.linux64_64_download=android-ndk64-r10-linux-x86_64.tar.bz2
+ndk.linux64_64_bytes=339708042
+ndk.linux64_64_checksum=737290195583268b7fbff4aa56465ab6
+
+ndk.linux32_64_download=android-ndk64-r10-linux-x86.tar.bz2
+ndk.linux32_64_bytes=338544906
+ndk.linux32_64_checksum=bea5d027baeb948cbff6af840d26b80d
+
+ndk.win64_64_download=android-ndk64-r10-windows-x86_64.zip
+ndk.win64_64_bytes=417411195
+ndk.win64_64_checksum=91879ec85539b45313a21b9526b911a8
+
+ndk.win32_64_download=android-ndk64-r10-windows-x86.zip
+ndk.win32_64_bytes=396751892
+ndk.win32_64_checksum=f79070ace2cde9ebf6a2e2be4a61ac7a
+
+ndk.debug_info_download=android-ndk-r10-cxx-stl-libs-with-debug-info.zip
+ndk.debug_info_bytes=253198908
+ndk.debug_info_checksum=c2a90c43d17dbb5f0609cc8237491788
 
 
 page.title=Android NDK
@@ -56,129 +82,261 @@
 This is the Android Software Development Kit License Agreement
 
 <h3>1. Introduction</h3>
-1.1 The Android Software Development Kit (referred to in this License Agreement as the "SDK" and specifically including the Android system files, packaged APIs, and Google APIs add-ons) 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 SDK.
+1.1 The Android Software Development Kit (referred to in this License Agreement as the "SDK" and
+specifically including the Android system files, packaged APIs, and Google APIs add-ons) 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 SDK.
 
-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.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.
+1.3 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600
+Amphitheatre Parkway, Mountain View, CA 94043, United States.
 
 
 <h3>2. Accepting this License Agreement</h3>
-2.1 In order to use the SDK, you must first agree to this License Agreement. You may not use the SDK if you do not accept this License Agreement.
+2.1 In order to use the SDK, you must first agree to this License Agreement. You may not use the
+SDK 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 SDK and may not accept the License Agreement if you are a person barred from receiving the SDK under the laws of the United States or other countries including the country in which you are resident or from which you use the SDK.
+2.3 You may not use the SDK and may not accept the License Agreement if you are a person barred
+from receiving the SDK under the laws of the United States or other countries including the country
+in which you are resident or from which you use the SDK.
 
-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 SDK on behalf of your employer or other entity.
+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 SDK on behalf of your employer or other entity.
 
 
 <h3>3. SDK License from Google</h3>
-3.1 Subject to the terms of this License Agreement, Google grants you a limited, worldwide, royalty-free, non-assignable and non-exclusive license to use the SDK solely to develop applications to run on the Android platform.
+3.1 Subject to the terms of this License Agreement, Google grants you a limited, worldwide,
+royalty-free, non-assignable and non-exclusive license to use the SDK solely to develop
+applications to run on the Android platform.
 
-3.2 You agree that Google or third parties own all legal right, title and interest in and to the SDK, including any Intellectual Property Rights that subsist in the SDK. "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.2 You agree that Google or third parties own all legal right, title and interest in and to the
+SDK, including any Intellectual Property Rights that subsist in the SDK. "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 SDK 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 SDK or any part of the SDK; or (b) load any part of the SDK onto a mobile handset or any other hardware device except a personal computer, combine any part of the SDK with other software, or distribute any software or device incorporating a part of the SDK.
+3.3 You may not use the SDK 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 SDK or any part of the SDK; or (b) load any part of the SDK onto a mobile
+handset or any other hardware device except a personal computer, combine any part of the SDK with
+other software, or distribute any software or device incorporating a part of the SDK.
 
-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 SDK.
+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 SDK.
 
-3.5 Use, reproduction and distribution of components of the SDK 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.5 Use, reproduction and distribution of components of the SDK 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 SDK that Google provides may change without prior notice to you and that future versions of the SDK may be incompatible with applications developed on previous versions of the SDK. You agree that Google may stop (permanently or temporarily) providing the SDK (or any features within the SDK) to you or to users generally at Google's sole discretion, without prior notice to you.
+3.6 You agree that the form and nature of the SDK that Google provides may change without prior
+notice to you and that future versions of the SDK may be incompatible with applications developed
+on previous versions of the SDK. You agree that Google may stop (permanently or temporarily)
+providing the SDK (or any features within the SDK) 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.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 SDK.
+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 SDK.
 
 
 <h3>4. Use of the SDK by You</h3>
-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 SDK, including any intellectual property rights that subsist in those applications.
+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 SDK, including
+any intellectual property rights that subsist in those applications.
 
-4.2 You agree to use the SDK and write applications only for purposes that are permitted by (a) this 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.2 You agree to use the SDK and write applications only for purposes that are permitted by (a)
+this 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 SDK 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.3 You agree that if you use the SDK 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 SDK, 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 or any mobile communications carrier.
+4.4 You agree that you will not engage in any activity with the SDK, 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 or any mobile communications carrier.
 
-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.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 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.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.
 
 
 <h3>5. Your Developer Credentials</h3>
-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.
+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.
 
 
 <h3>6. Privacy and Information</h3>
-6.1 In order to continually innovate and improve the SDK, 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 SDK are being used and how they are being used. Before any of this information is collected, the SDK will notify you and seek your consent. If you withhold consent, the information will not be collected.
+6.1 In order to continually innovate and improve the SDK, 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 SDK
+are being used and how they are being used. Before any of this information is collected, the SDK
+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 SDK and is maintained in accordance with Google's Privacy Policy.
+6.2 The data collected is examined in the aggregate to improve the SDK and is maintained in
+accordance with Google's Privacy Policy.
 
 
 <h3>7. Third Party Applications</h3>
-7.1 If you use the SDK 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.1 If you use the SDK 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.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.
+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.
 
 
 <h3>8. Using Android APIs</h3>
 8.1 Google Data 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.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.
+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.
 
 
 <h3>9. Terminating this License Agreement</h3>
-9.1 This License Agreement will continue to apply until terminated by either you or Google as set out below.
+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 SDK and any relevant developer credentials.
+9.2 If you want to terminate this License Agreement, you may do so by ceasing your use of the SDK
+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 SDK (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the SDK to you; or
-(D) Google decides to no longer provide the SDK or certain parts of the SDK to users in the country in which you are resident or from which you use the service, or the provision of the SDK or certain SDK services to you by Google is, in Google's sole discretion, no longer commercially viable.
+(C) the partner with whom Google offered certain parts of SDK (such as APIs) to you has terminated
+its relationship with Google or ceased to offer certain parts of the SDK to you; or
+(D) Google decides to no longer provide the SDK or certain parts of the SDK to users in the country
+in which you are resident or from which you use the service, or the provision of the SDK or certain
+SDK 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.
+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.
 
 
 <h3>10. DISCLAIMER OF WARRANTIES</h3>
-10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
+10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND THAT THE
+SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
 
-10.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE SDK 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.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE
+SDK 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.
+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.
 
 
 <h3>11. LIMITATION OF LIABILITY</h3>
-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.
+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.
 
 
 <h3>12. Indemnification</h3>
-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 SDK, (b) any application you develop on the SDK 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.
+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 SDK, (b) any application you develop on the SDK 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.
 
 
 <h3>13. Changes to the License Agreement</h3>
-13.1 Google may make changes to the License Agreement as it distributes new versions of the SDK. When these changes are made, Google will make a new version of the License Agreement available on the website where the SDK is made available.
+13.1 Google may make changes to the License Agreement as it distributes new versions of the SDK.
+When these changes are made, Google will make a new version of the License Agreement available on
+the website where the SDK is made available.
 
 
 <h3>14. General Legal Terms</h3>
-14.1 This License Agreement constitutes the whole legal agreement between you and Google and governs your use of the SDK (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 SDK.
+14.1 This License Agreement constitutes the whole legal agreement between you and Google and
+governs your use of the SDK (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 SDK.
 
-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.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.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.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 SDK 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 SDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
+14.5 EXPORT RESTRICTIONS. THE SDK 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 SDK. 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.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.
+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.
 
 
 <em>November 13, 2012</em>
@@ -272,6 +430,182 @@
  <p>
    <a href="#" onclick="return toggleContent(this)"> <img
      src="/assets/images/triangle-opened.png" class="toggle-content-img" alt=""
+   >Android NDK, Revision 10</a> <em>(July 2014)</em>
+ </p>
+ <div class="toggle-content-toggleme">
+    <dl>
+      <dt>Important changes:</dt>
+      <dd>
+      <ul>
+        <li>Added 3 new ABIs, all 64-bit: arm64-v8a, x86_64, mips64.</li> Note that:
+        <ul>
+           <li>GCC 4.9 is the default compiler for 64-bit ABIs. Clang is currently version 3.4.
+<code>NDK_TOOLCHAIN_VERSION=clang</code>
+      may not work for arm64-v8a and mips64.</li>
+           <li>Android API level L is the first level with 64-bit support.  Note that this API
+level is a temporary one, and only for L-preview. An actual API level number will replace it at
+L-release.</li>
+           <li>This release includes now includes <code>all32</code> and <code>all64</code>
+settings for <code>APP_ABI</code>.
+              <ul>
+              <li><code>APP_ABI=all32</code> is equivalent to
+<code>APP_ABI=armeabi,armeabi-v7a,x86,mips</code>.</li>
+              <li><code>APP_ABI=all64</code> is equivalent to
+<code>APP_ABI=arm64-v8a,x86_64,mips64</code>.</li>
+              <li><code>APP_ABI=all</code> selects all ABIs.</li>
+              </ul>
+           <li>The new GNU libstdc++ in Android-L contains all <code>&lt;tr1/cmath&gt;</code>
+Before defining your own math function, check <code>_GLIBCXX_USE_C99_MATH_TR1</code> to see a
+function with that name already exists, in order to avoid "multiple definition" errors from the
+linker.</li>
+           <li>The cpu-features library has been updated for the ARMv8 kernel.  The existing
+cpu-features library may fail to detect the presence of NEON on the ARMv8 platform. Recompile your
+code with the new version.</li>
+        </ul>
+        <li>Added a new <code>platforms/android-L/</code> API directory. It includes:</li>
+        <ul>
+           <li>Updated Bionic headers, which had not changed from Android API levels 3
+(Cupcake) to 19 (KitKat). This new version, for level L, is to be synchronized with AOSP.</li>
+           <li>New media APIs and a native-codec sample.</li>
+           <li>An updated <code>Android.h</code> header for SLES/OpenSLES, enabling support for
+single-precision, floating-point audio format in AudioPlayer.</li>
+           <li>GLES 3.1 and AEP extensions to <code>libGLESv3.so.</code></li>
+           <li>GLES2 and GLES3 headers updated to the latest official Khronos versions.</li>
+        </ul>
+        <li>Added GCC 4.9 compilers to the 32-/64-bit ABIs.  GCC 4.9 is the default (only) compiler
+for 64-bit ABIs, as previously mentioned.  For 32-bit ABIs, you must explcitly enable GCC 4.9, as
+GCC 4.6 is still the default.</li>
+        <ul>
+           <li>For ndk-build, enable 32-bit, GCC 4.9 building either by adding
+<code>NDK_TOOLCHAIN_VERSION=4.9</code> to <code>Application.mk</code>, or exporting it as an
+environment variable from the command line.</li>
+           <li>For a standalone toolchain, use the <code> --toolchain=</code> option in the
+<code>make-standalone-toolchain.sh</code> script. For example: <code>--toolchain=arm-linux-androideabi-4.9.</code></li>
+        </ul>
+        <li>Upgraded GDB to version 7.6 in GCC 4.8/4.9 and x86*. Since GDB is still at version GDB-7.3.x in
+GCC 4.6 (the default for ARM and MIPS), you must set
+<code>NDK_TOOLCHAIN_VERSION=4.8</code> or <code>4.9</code> to enable ndk-gdb to select GDB 7.6.</li>
+        <li>Added the <code>-mssse3</code> build option to provide SSSE3 support, and made it the default for ABI x86
+(upgrading from SSE3). The image released by Google does not contain SSSE3 instructions.</li>
+        <li>Updated GCC 4.8 to 4.8.3.</li>
+        <li>Improved ARM libc++ EH support by switching from gabi++ to libc++abi. For details, see the "C++ Support" section of the documentation.
+  Note that:</li>
+        <ul>
+           <li>All tests except for locale now pass for Clang 3.4 and GCC 4.8. For more
+information, see the "C++ Support" section of the documentation.</li>
+           <li>The libc++ libraries for X86 and MIPS libc++ still use gabi++.</li>
+           <li>GCC 4.7 and later can now use &lt;atomic&gt;.</li>
+           <li>You must add <code>-fno-strict-aliasing</code> if you use <code> &lt;list&gt;</code>, because <code>__list_imp::_end</code>_ breaks
+      TBAA rules.  (Issue <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61571">61571</a>.)</li>
+           <li>As of GCC 4.6, LIBCXX_FORCE_REBUILD:=true no longer rebuilds libc++. Rebuilding it
+requires the use of a different compiler. Note that Clang 3.3 is untested.</li>
+        </ul>
+        <li>mclinker is now version 2.7, and has aarch64 Linux support.</li>
+        <li>Added precompiled header support for headers specified by <code>LOCAL_PCH</code>.  (Issue <a href="http://b.android.com/25412">25412</a>).</li>
+      </dd>
+   <dl>
+
+
+     <dt>Important bug fixes:</dt>
+     <dd>
+     <ul>
+       <li>Fixed libc++ so that it now compiles <code>std::feof</code>, etc. (Issue <a
+href="http://b.android.com/66668">66668</a>).</li>
+       <li>Fixed a Clang 3.3/3.4 atomic library call that caused crashes in some of the libc++
+tests for ABI armeabi.</li>
+       <li>Fixed Clang 3.4 crashes that were occurring on reading precompiled headers. (Issue <a
+href="http://b.android.com/66657">66657</a>).</li>
+       <li>Fixed the Clang 3.3/3.4 <code>-O3</code> assert on:</li>
+       <code>llvm-3.2/llvm/include/llvm/MDBuilder.h:64: llvm::MDNode*
+llvm::MDBuilder::createBranchWeights(llvm::ArrayRef<unsigned int>): Assertion Weights.size() >= 2
+&& "Need at least two branch weights!"</code> (Issue <a href="http://b.android.com/57381">57381</a>).
+       <li>Fixed the following Clang 3.3/3.4 crash:</li>
+       <code>Assertion failed: (!Fn && "cast failed but able to resolve overload expression!!"), function CheckCXXCStyleCast, file
+Volumes/data/ndk-toolchain/src/llvm-3.3/llvm/tools/clang/lib/Sema/SemaCast.cpp, line 2018</code>.
+(Issue <a href="http://b.android.com/66950">66950</a>).
+     </ul>
+     </dd>
+
+     <dt>Other bug fixes:</dt>
+     <dd>
+     <ul>
+       <li>Fixed headers:</li>
+       <ul>
+          <li>Fixed 32-bit <code>ssize_t</code> to be <code>int</code> instead of <code>long
+int</code>.</li>
+          <li>Fixed <code>WCHAR_MIN</code> and <code>WCHAR_MAX</code> so that they they take
+appropriate signs according to the architecture they're running on:</li>
+          <ul>
+             <li>X86/MIPS: signed.
+             <li>ARM: unsigned.
+             <li>To force X86/MIPS to default to unsigned, use
+<code>-D__WCHAR_UNSIGNED__</code>.</li>
+             <li>To force <code>wchar_t</code> to be 16 bits, use <code>-fshort-wchar</code>.</li>
+          </ul>
+          <li>Removed non-existent symbols from 32-bit <code>libc.so</code>, and added <code>pread64</code>,
+<code>pwrite64</code>, <code>ftruncate64</code> for
+Android API level 12 and higher. (Issue <a href="http://b.android.com/69319">69319</a>). For more
+information, see the commit message accompanying AOSP change list
+     <a href="https://android-review.googlesource.com/#/c/94137">94137</a>.</li>
+       </ul>
+       <li>Fixed GCC warning about redefinition of <code>putchar</code>. Warning message reads:</li>
+       <code>include/stdio.h:236:5: warning: conflicts with previous declaration here
+[-Wattributes] int  putchar(int);</code> (Change list <a
+href="https://android-review.googlesource.com/#/c/91185">91185</a>).
+       <li>Fixed <code>make-standalone-toolchain.sh --stl=libc++</code> so that it:</li>
+       <ul>
+          <li>Copies <code>cxxabi.h</code>. (Issue <a
+href="http://b.android.com/68001">68001</a>).</li>
+          <li>Runs in directories other than the NDK install directory. (Issues <a
+href="http://b.android.com/67690">67690</a> and <a href="http://b.android.com/68647">68647</a>).</li>
+       </ul>
+       <li>Fixed GCC/Windows to quote arguments only when necessary for spawning processes in
+external programs. This change decreases the likelihood of exceeding the 32K length limit.</li>
+       <li>Fixed an issue that made it impossible to adjust the <code>APP_PLATFORM</code>
+environment variable.</li>
+       <li>Fixed the implementation of <code>IsSystemLibrary()</code> in crazy_linker so that it
+uses <code>strrchr()</code>
+  instead of <code>strchr()</code> to find the library path's true basename.</li>
+       <li>Fixed native-audio's inability to build in debug mode.</li>
+       <li>Fixed gdb's inability to print extreme floating-point numbers. (Issue <a
+href="http://b.android.com/69203">69203</a>).</li>
+       <li>Fixed Clang 3.4 inability to compile with <code>-Wl,-shared</code> (as opposed to
+<code>-shared</code>, which
+  had no compilation issues).  The problem was that Clang added <code>-pie</code> for Android
+targets if neither <code>-shared</code> nor <code>-static</code> existed. This behavior, which was
+incorrect, caused the linker to complain that <code>-shared</code> and <code>-pie</code> could not
+co-exist.</li>
+
+     </ul>
+     </dd>
+
+
+     <dt>Other changes:</dt>
+     <dd>
+     <ul>
+        <li>Added <code>arm_neon.h</code> to the x86 toolchain so that it now emulates ~47% of
+Neon. There is currently no support for 64-bit types. For more information, see the section on ARM
+Neon intrinsics support in the x86 documentation.</li>
+        <li>Ported ARM/GOT_PREL optimization (present in GCC 4.6 built from the GCC google branch) to
+ARM GCC 4.8/4.9.  This optimization sometimes reduces instruction count when accessing global
+variables.  As an example, see the build.sh script in
+<code>$NDK/tests/build/b14811006-GOT_PREL-optimization/</code>.</li>
+        <li>Added ARM version for STL gabi++, stlport, and libc++. They now have both it and Thumb
+mode.</li>
+        <li>It is now possible to call the make-standalone-toolchain.sh script with
+<code>--toolchain=x86_64-linux-android-4.9</code>, which is equivalent to
+<code>--toolchain=x86_64-4.9</code>.</li>
+     </dd>
+     </ul>
+   </dl>
+ </div>
+</div>
+
+
+<div class="toggle-content closed">
+ <p>
+   <a href="#" onclick="return toggleContent(this)"> <img
+     src="/assets/images/triangle-closed.png" class="toggle-content-img" alt=""
    >Android NDK, Revision 9d</a> <em>(March 2014)</em>
  </p>
  <div class="toggle-content-toggleme">
@@ -284,7 +618,7 @@
 still the default compiler.</li>
         <li>Added <code>APP_ABI=armeabi-v7a-hard</code>, with
 additional multilib option <code>-mfloat-abi=hard</code>. These options are for
-use with ARM GCC 4.6/4.8 and clang 3.3/3.4 (which use 4.8's assembler, linker,
+use with ARM GCC 4.6/4.8 and Clang 3.3/3.4 (which use 4.8's assembler, linker,
 and libs). When using these options, note the following changes:</li>
         <ul>
            <li> When executing the <code>ndk-build</code> script, add the
@@ -322,7 +656,7 @@
         </ul>
         For more information, see
 <code>CPLUSPLUS-SUPPORT.html</code>.
-(Issue <a href="b.android.com/36496">36496</a>)</li>
+(Issue <a href="http://b.android.com/36496">36496</a>)</li>
       </ul>
       </dd>
    <dl>
@@ -337,7 +671,7 @@
   a dependent, non-type template argument. (GCC Issue <a
 href="http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59052">59052</a>)</li>
        <li>Added more modules to prebuilt python (Issue <a
-href="b.android.com/59902">59902</a>):
+href="http://b.android.com/59902">59902</a>):
                <ul>
                  <li>Mac OS X: <code>zlib</code>, <code>bz2</code>,
 <code>_curses</code>, <code>_curses_panel</code>, <code>_hashlib</code>,
@@ -637,7 +971,8 @@
           (<a href="http://b.android.com/55826">Issue 55826</a>)</li>
         <li>Fixed Clang 3.3 MIPS compiler problem where HI and LO registers are incorrectly
           reused.</li>
-        <li>Fixed issue with MIPS 4.7 ICE in {@code dbx_reg_number}. The error message is as follows:
+        <li>Fixed issue with MIPS 4.7 ICE in {@code dbx_reg_number}. The error message is as
+follows:
 <pre>
 external/icu4c/i18n/decimfmt.cpp:1322:1:
 internal compiler error: in dbx_reg_number, at dwarf2out.c:10185
@@ -721,11 +1056,13 @@
         <li>Modified GCC builds so that all {@code libgcc.a} files are built with
           <code>-funwind-tables</code> to allow the stack to be unwound past previously blocked
           points, such as <code>__aeabi_idiv0</code>.</li>
-        <li>Added Ingenic MXU support in MIPS GCC4.6/4.7/4.8 with new <code>-mmxu</code> option.</li>
+        <li>Added Ingenic MXU support in MIPS GCC4.6/4.7/4.8 with new <code>-mmxu</code>
+option.</li>
         <li>Extended MIPS GCC4.6/4.7/4.8 <code>-mldc1-sdc1</code> to control ldxc1/sdxc1 too</li>
         <li>Added crazy linker. For more information, see
           {@code sources/android/crazy_linker/README.TXT}.</li>
-        <li>Fixed {@code bitmap-plasma} to draw to full screen rather than a 200x200 pixel area.</li>
+        <li>Fixed {@code bitmap-plasma} to draw to full screen rather than a 200x200 pixel
+area.</li>
         <li>Reduced linux and darwin toolchain sizes by 25% by creating symlinks to identical files.
           </li>
       </ul>
@@ -935,7 +1272,8 @@
           <li>Added two flags to re-enable two optimizations in upstream Clang but disabled in
               NDK for better compatibility with code compiled by GCC:
             <ul>
-              <li>Added a {@code -fcxx-missing-return-semantics} flag to re-enable <em>missing return
+              <li>Added a {@code -fcxx-missing-return-semantics} flag to re-enable <em>missing
+return
                 semantics</em> in Clang 3.2+. Normally, all paths should terminate with a return
                 statement for a value-returning function. If this is not the case, clang inserts
                 an undefined instruction (or trap in debug mode) at the path without a return
@@ -1079,7 +1417,8 @@
             (<a href="https://android-review.googlesource.com/#/c/52134">Change 52134</a>)</li>
           <li>Fixed Clang 3.1 internal compiler error when using Eigen library.
             (<a href="http://b.android.com/41246">Issue 41246</a>)</li>
-          <li>Fixed Clang 3.1 internal compiler error including {@code &lt;chrono&gt;} in C++11 mode.
+          <li>Fixed Clang 3.1 internal compiler error including {@code &lt;chrono&gt;} in C++11
+mode.
             (<a href="http://b.android.com/39600">Issue 39600</a>)</li>
           <li>Fixed Clang 3.1 internal compiler error when generating object code for a method
             call to a uniform initialized {@code rvalue}.
@@ -1088,7 +1427,8 @@
             (<a href="https://android-review.googlesource.com/#/c/52154">Change 52154</a>)</li>
           <li>Fixed problem with GNU Debugger (GDB) SIGILL when debugging on Android 4.1.2.
             (<a href="http://b.android.com/40941">Issue 40941</a>)</li>
-          <li>Fixed problem where GDB cannot set {@code source:line} breakpoints when symbols contain
+          <li>Fixed problem where GDB cannot set {@code source:line} breakpoints when symbols
+contain
             long, indirect file paths.
             (<a href="http://b.android.com/42448">Issue 42448</a>)</li>
           <li>Fixed GDB {@code read_program_header} for MIPS PIE executables.
@@ -1182,7 +1522,8 @@
           <li>Enabled MIPS floating-point {@code madd/msub/nmadd/nmsub/recip/rsqrt}
             instructions with 32-bit FPU.</li>
           <li>Enabled graphite loop optimizer in GCC 4.6 and 4.7 to allow more optimizations:
-            {@code -fgraphite}, {@code -fgraphite-identity}, {@code -floop-block}, {@code -floop-flatten},
+            {@code -fgraphite}, {@code -fgraphite-identity}, {@code -floop-block}, {@code
+-floop-flatten},
             {@code -floop-interchange}, {@code -floop-strip-mine}, {@code -floop-parallelize-all},
             and {@code -ftree-loop-linear}.
             (<a href="http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">info</a>)</li>
@@ -1379,7 +1720,9 @@
           <li>Updated {@code make-standalone-toolchain.sh} to accept the suffix {@code -clang3.1}
             which is equivalent to adding {@code --llvm-version=3.1} to the GCC 4.6 toolchain.</li>
           <li>Updated GCC and Clang bug report URL to:
-            <a href="http://source.android.com/source/report-bugs.html">http://source.android.com/source/report-bugs.html</a></li>
+            <a
+href="http://source.android.com/source/report-bugs.html">http://source.android.com/source/report-bug
+s.html</a></li>
           <li>Added ARM ELF support to {@code llvm-objdump}.</li>
           <li>Suppressed <em>treating c input as c++</em> warning for Clang builds.</li>
           <li>Updated build so that only the 32-bit version of {@code libiberty.a} is built and
@@ -1418,7 +1761,8 @@
             <a href="http://code.google.com/p/android/issues/list">report any issues</a>.</p></li>
           <li>Added Gold linker {@code ld.gold} for the Windows toolchain. Gold linker is also the
             default for ARM and X86 on all hosts. You may override it to use the {@code ld.bfd}
-            linker by adding {@code LOCAL_LDFLAGS += -fuse-ld=bfd} to {@code Android.mk}, or by passing
+            linker by adding {@code LOCAL_LDFLAGS += -fuse-ld=bfd} to {@code Android.mk}, or by
+passing
             {@code -fuse-ld=bfd} to the g++/clang++ command line that does the linking.</li>
           <li>Added checks for spaces in the NDK path to the {@code ndk-build[.cmd]} and
             {@code ndk-gdb} scripts, to prevent build errors that are difficult to diagnose.</li>
@@ -1605,7 +1949,8 @@
               <li>Replaced {@code link.h} for MIPS with new version supporting all platforms.</li>
               <li>Removed {@code linux-unistd.h}</li>
               <li>Move GLibc-specific macros {@code LONG_LONG_MIN}, {@code LONG_LONG_MAX} and
-              {@code ULONG_LONG_MAX} from {@code &lt;pthread.h&gt;} to {@code &lt;limits.h&gt;}.</li>
+              {@code ULONG_LONG_MAX} from {@code &lt;pthread.h&gt;} to {@code
+&lt;limits.h&gt;}.</li>
             </ul>
           </li>
           <li>Fixed a buffer overflow in {@code ndk-stack-parser}.</li>
@@ -1767,7 +2112,8 @@
                   <li>Fixed ARM {@code strip} command to preserve the original {@code p_align} and
 {@code p_flags} in {@code GNU_RELRO} section if they are valid. Without this fix, programs
 built with {@code -fPIE} could not be debugged. (<a
-href="http://sourceware.org/cgi-bin/cvsweb.cgi/src/bfd/elf.c.diff?cvsroot=src&r1=1.552&r2=1.553">more info</a>)</li>
+href="http://sourceware.org/cgi-bin/cvsweb.cgi/src/bfd/elf.c.diff?cvsroot=src&r1=1.552&r2=1.553">mor
+e info</a>)</li>
                 </ul>
               </li>
               <li>Disabled {@code sincos()} optimization for compatibility with older
@@ -2505,7 +2851,8 @@
 
             </li>
 
-            <li>You can build a standalone x86 toolchain using the <code>--toolchain=x86-4.4.3</code>
+            <li>You can build a standalone x86 toolchain using the
+<code>--toolchain=x86-4.4.3</code>
             option when calling <code>make-standalone-toolchain.sh</code>. See
             <code>docs/STANDALONE-TOOLCHAIN.html</code> for more details.
             </li>
@@ -2548,7 +2895,8 @@
           <li>Fixed a bug where code linked against <code>gnustl_static</code> crashed when run on
           platform releases older than API level 8 (Android 2.2).</li>
 
-          <li><code>ndk-gdb</code>: Fixed a bug that caused a segmentation fault when debugging Android 3.0
+          <li><code>ndk-gdb</code>: Fixed a bug that caused a segmentation fault when debugging
+Android 3.0
           or newer devices.</li>
 
           <li><code>&lt;android/input.h&gt;</code>: Two functions that were introduced in API level
@@ -2617,12 +2965,14 @@
   </p>
 
   <div class="toggle-content-toggleme">
-      <p>This release of the NDK does not include any new features compared to r5. The r5b release addresses the
+      <p>This release of the NDK does not include any new features compared to r5. The r5b release
+addresses the
       following problems in the r5 release:
       </p>
       <ul>
     <li>The r5 binaries required glibc 2.11, but the r5b binaries are generated with a special
-    toolchain that targets glibc 2.7 or higher instead. The Linux toolchain binaries now run on Ubuntu 8.04 or higher. </li>
+    toolchain that targets glibc 2.7 or higher instead. The Linux toolchain binaries now run on
+Ubuntu 8.04 or higher. </li>
     <li>Fixes a compiler bug in the arm-linux-androideabi-4.4.3 toolchain.
     The previous binary generated invalid thumb instruction sequences when
     dealing with signed chars.</li>
@@ -2642,21 +2992,29 @@
         with the new NDK toolchain.</li>
         <li>Builds in Cygwin are faster by avoiding calls to <code>cygpath -m</code>
         from GNU Make for every source or object file, which caused problems
-        with very large source trees. In case this doesn't work properly, define <code>NDK_USE_CYGPATH=1</code> in your
+        with very large source trees. In case this doesn't work properly, define
+<code>NDK_USE_CYGPATH=1</code> in your
         environment to use <code>cygpath -m</code> again.</li>
-        <li>The Cygwin installation now notifies the user of invalid installation paths that contain spaces. Previously, an invalid path
-        would output an error that complained about an incorrect version of GNU Make, even if the right one was installed.
+        <li>The Cygwin installation now notifies the user of invalid installation paths that
+contain spaces. Previously, an invalid path
+        would output an error that complained about an incorrect version of GNU Make, even if the
+right one was installed.
       </ul>
     </li>
-  <li>Fixed a typo that prevented the <code>NDK_MODULE_PATH</code> environment variable from working properly when
+  <li>Fixed a typo that prevented the <code>NDK_MODULE_PATH</code> environment variable from
+working properly when
   it contained multiple directories separated with a colon. </li>
   <li>The <code>prebuilt-common.sh</code> script contains fixes to check the compiler for 64-bit
   generated machine code, instead of relying on the host tag, which
-  allows the 32-bit toolchain to rebuild properly on Snow Leopard. The toolchain rebuild scripts now also support
+  allows the 32-bit toolchain to rebuild properly on Snow Leopard. The toolchain rebuild scripts
+now also support
   using a 32-bit host toolchain.</li>
-  <li>A missing declaration for <code>INET_ADDRSTRLEN</code> was added to <code>&lt;netinet/in.h&gt;</code>.</li>
-  <li>Missing declarations for <code>IN6_IS_ADDR_MC_NODELOCAL</code> and <code>IN6_IS_ADDR_MC_GLOBAL</code> were added to <code>&lt;netinet/in6.h&gt;</code>.</li>
-  <li>'asm' was replaced with '__asm__' in <code>&lt;asm/byteorder.h&gt;</code> to allow compilation with <code>-std=c99</code>.</li>
+  <li>A missing declaration for <code>INET_ADDRSTRLEN</code> was added to
+<code>&lt;netinet/in.h&gt;</code>.</li>
+  <li>Missing declarations for <code>IN6_IS_ADDR_MC_NODELOCAL</code> and
+<code>IN6_IS_ADDR_MC_GLOBAL</code> were added to <code>&lt;netinet/in6.h&gt;</code>.</li>
+  <li>'asm' was replaced with '__asm__' in <code>&lt;asm/byteorder.h&gt;</code> to allow
+compilation with <code>-std=c99</code>.</li>
   </ul>
   </div>
   </div>
@@ -2673,8 +3031,10 @@
          of native code. Using the APIs, developers have direct native access to events, audio,
          graphics and window management, assets, and storage. Developers can also implement the
          Android application lifecycle in native code with help from the new
-         {@link android.app.NativeActivity} class. For detailed information describing the changes in this
-         release, read the <code>CHANGES.HTML</code> document included in the downloaded NDK package.
+         {@link android.app.NativeActivity} class. For detailed information describing the changes
+in this
+         release, read the <code>CHANGES.HTML</code> document included in the downloaded NDK
+package.
       </p>
       <dl>
         <dt>General notes:</dt>
@@ -2703,35 +3063,46 @@
               </ul>
             </li>
 
-            <li>Includes a new toolchain (based on GCC 4.4.3), which generates better code, and can also now
+            <li>Includes a new toolchain (based on GCC 4.4.3), which generates better code, and can
+also now
             be used as a standalone cross-compiler, for people who want to build their stuff with
             <code>./configure &amp;&amp; make</code>. See
-            docs/STANDALONE-TOOLCHAIN.html for the details. The binaries for GCC 4.4.0 are still provided,
+            docs/STANDALONE-TOOLCHAIN.html for the details. The binaries for GCC 4.4.0 are still
+provided,
             but the 4.2.1 binaries were removed.</li>
 
-            <li>Adds support for prebuilt static and shared libraries (docs/PREBUILTS.html) and module
+            <li>Adds support for prebuilt static and shared libraries (docs/PREBUILTS.html) and
+module
             exports and imports to make sharing and reuse of third-party modules much easier
             (docs/IMPORT-MODULE.html explains why).</li>
 
-            <li>Provides a default C++ STL implementation (based on STLport) as a helper module. It can be used either
-            as a static or shared library (details and usage examples are in sources/android/stlport/README). Prebuilt
-            binaries for STLport (static or shared) and GNU libstdc++ (static only) are also provided if you choose to
+            <li>Provides a default C++ STL implementation (based on STLport) as a helper module. It
+can be used either
+            as a static or shared library (details and usage examples are in
+sources/android/stlport/README). Prebuilt
+            binaries for STLport (static or shared) and GNU libstdc++ (static only) are also
+provided if you choose to
             compile against those libraries instead of the default C++ STL implementation.
-            C++ Exceptions and RTTI are not supported in the default STL implementation. For more information, see
+            C++ Exceptions and RTTI are not supported in the default STL implementation. For more
+information, see
             docs/CPLUSPLUS-SUPPORT.HTML.</li>
 
-            <li>Includes improvements to the <code>cpufeatures</code> helper library that improves reporting
-            of the CPU type (some devices previously reported ARMv7 CPU when the device really was an ARMv6). We
+            <li>Includes improvements to the <code>cpufeatures</code> helper library that improves
+reporting
+            of the CPU type (some devices previously reported ARMv7 CPU when the device really was
+an ARMv6). We
             recommend developers that use this library to rebuild their applications then
             upload to Google Play to benefit from the improvements.</li>
 
             <li>Adds an EGL library that lets you create and manage OpenGL ES textures and
               services.</li>
 
-            <li>Adds new sample applications, <code>native-plasma</code> and <code>native-activity</code>,
+            <li>Adds new sample applications, <code>native-plasma</code> and
+<code>native-activity</code>,
             to demonstrate how to write a native activity.</li>
 
-            <li>Includes many bugfixes and other small improvements; see docs/CHANGES.html for a more
+            <li>Includes many bugfixes and other small improvements; see docs/CHANGES.html for a
+more
               detailed list of changes.</li>
           </ul>
         </dd>
@@ -3043,7 +3414,8 @@
       </table>
 
       <p>For more information about API Level and its relationship to Android platform versions,
-      see <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">Android API Levels</a>.</p>
+      see <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">Android API
+Levels</a>.</p>
     </li>
 
     <li>Additionally, an application using the OpenGL ES APIs should declare a
@@ -3061,7 +3433,8 @@
 </pre>
 
       <p>For more information, see the <a href=
-      "{@docRoot}guide/topics/manifest/uses-feature-element.html"><code>&lt;uses-feature&gt;</code></a>
+
+"{@docRoot}guide/topics/manifest/uses-feature-element.html"><code>&lt;uses-feature&gt;</code></a>
       documentation.</p>
     </li>
 
@@ -3104,7 +3477,8 @@
 
   <p>Before you get started make sure that you have downloaded the latest <a href=
   "{@docRoot}sdk/index.html">Android SDK</a> and upgraded your applications and environment as
-  needed. The NDK is compatible with older platform versions but not older versions of the SDK tools.
+  needed. The NDK is compatible with older platform versions but not older versions of the SDK
+tools.
   Also, take a moment to review the <a href="#Reqs">System and
 Software Requirements</a>
   for the NDK, if you haven't already.</p>
@@ -3178,7 +3552,8 @@
     <p>Write a native activity, which allows you to implement the lifecycle callbacks in native
     code. The Android SDK provides the {@link android.app.NativeActivity} class, which is a
     convenience class that notifies your
-    native code of any activity lifecycle callbacks (<code>onCreate()</code>, <code>onPause()</code>,
+    native code of any activity lifecycle callbacks (<code>onCreate()</code>,
+<code>onPause()</code>,
     <code>onResume()</code>, etc). You can implement the callbacks in your native code to handle
     these events when they occur. Applications that use native activities must be run on Android
     2.3 (API Level 9) or later.</p>
@@ -3230,7 +3605,8 @@
   difference between the two instruction sets is that ARMv7-A supports hardware FPU, Thumb-2, and
   NEON instructions. You can target either or both of the instruction sets &mdash; ARMv5TE is the
   default, but switching to ARMv7-A is as easy as adding a single line to the application's
-  <code>Application.mk</code> file, without needing to change anything else in the file. You can also build for
+  <code>Application.mk</code> file, without needing to change anything else in the file. You can
+also build for
   both architectures at the same time and have everything stored in the final <code>.apk</code>.
   Complete information is provided in the CPU-ARCH-ABIS.HTML in the NDK package.</p>
 
@@ -3311,13 +3687,15 @@
 
     <li>CHANGES.HTML &mdash; a complete list of changes to the NDK across all releases.</li>
 
-    <li>DEVELOPMENT.HTML &mdash; describes how to modify the NDK and generate release packages for it</li>
+    <li>DEVELOPMENT.HTML &mdash; describes how to modify the NDK and generate release packages for
+it</li>
 
     <li>HOWTO.HTML &mdash; information about common tasks associated with NDK development</li>
 
     <li>IMPORT-MODULE.HTML &mdash; describes how to share and reuse modules</li>
 
-    <li>LICENSES.HTML  &mdash; information about the various open source licenses that govern the Android NDK</li>
+    <li>LICENSES.HTML  &mdash; information about the various open source licenses that govern the
+Android NDK</li>
 
     <li>NATIVE-ACTIVITY.HTML &mdash; describes how to implement native activities</li>
 
diff --git a/docs/html/training/wearables/data-layer/events.jd b/docs/html/training/wearables/data-layer/events.jd
index 0146c4e..a37afe0 100644
--- a/docs/html/training/wearables/data-layer/events.jd
+++ b/docs/html/training/wearables/data-layer/events.jd
@@ -43,7 +43,8 @@
     &#64;Override
     public void onResult(final DataItemResult result) {
         if(result.getStatus().isSuccess()) {
-        Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+            Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+        }
     }
 });
 </pre>
@@ -293,7 +294,7 @@
     &#64;Override
     protected void onStop() {
         if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) {
-            Wearable.NodeApi.removeListener(mGoogleApiClient, this);
+            Wearable.DataApi.removeListener(mGoogleApiClient, this);
             mGoogleApiClient.disconnect();
         }
         super.onStop();
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 99596ef..5211762 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -158,7 +158,7 @@
         if (nativeCanvas == 0) {
             throw new IllegalStateException();
         }
-        mNativeCanvasWrapper = initCanvas(nativeCanvas);
+        mNativeCanvasWrapper = nativeCanvas;
         mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
         mDensity = Bitmap.getDefaultDensity();
     }
@@ -921,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(mNativeCanvasWrapper, r, g, b);
+        drawColor(Color.rgb(r, g, b));
     }
 
     /**
@@ -934,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(mNativeCanvasWrapper, a, r, g, b);
+        drawColor(Color.argb(a, r, g, b));
     }
 
     /**
@@ -944,7 +944,7 @@
      * @param color the color to draw onto the canvas
      */
     public void drawColor(int color) {
-        native_drawColor(mNativeCanvasWrapper, color);
+        native_drawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
     }
 
     /**
@@ -1298,13 +1298,28 @@
      */
     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(mNativeCanvasWrapper, bitmap.ni(), src, dst,
-                          paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity);
-    }
+      if (dst == null) {
+          throw new NullPointerException();
+      }
+      throwIfCannotDraw(bitmap);
+      final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+
+      float left, top, right, bottom;
+      if (src == null) {
+          left = top = 0;
+          right = bitmap.getWidth();
+          bottom = bitmap.getHeight();
+      } else {
+          left = src.left;
+          right = src.right;
+          top = src.top;
+          bottom = src.bottom;
+      }
+
+      native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), left, top, right, bottom,
+              dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+              bitmap.mDensity);
+  }
 
     /**
      * Draw the specified bitmap, scaling/translating automatically to fill
@@ -1334,8 +1349,23 @@
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst,
-                paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity);
+        final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+
+        int left, top, right, bottom;
+        if (src == null) {
+            left = top = 0;
+            right = bitmap.getWidth();
+            bottom = bitmap.getHeight();
+        } else {
+            left = src.left;
+            right = src.right;
+            top = src.top;
+            bottom = src.bottom;
+        }
+
+        native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), left, top, right, bottom,
+            dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+            bitmap.mDensity);
     }
 
     /**
@@ -1863,7 +1893,6 @@
     public static native void freeTextLayoutCaches();
 
     private static native long initRaster(long nativeBitmapOrZero);
-    private static native long initCanvas(long canvasHandle);
     private static native void native_setBitmap(long canvasHandle,
                                                 long bitmapHandle,
                                                 boolean copyState);
@@ -1916,11 +1945,6 @@
     private static native boolean native_quickReject(long nativeCanvas,
                                                      float left, float top,
                                                      float right, float bottom);
-    private static native void native_drawRGB(long nativeCanvas, int r, int g,
-                                              int b);
-    private static native void native_drawARGB(long nativeCanvas, int a, int r,
-                                               int g, int b);
-    private static native void native_drawColor(long nativeCanvas, int color);
     private static native void native_drawColor(long nativeCanvas, int color,
                                                 int mode);
     private static native void native_drawPaint(long nativeCanvas,
@@ -1962,16 +1986,9 @@
                                                  int screenDensity,
                                                  int bitmapDensity);
     private native void native_drawBitmap(long nativeCanvas, long nativeBitmap,
-                                                 Rect src, RectF dst,
-                                                 long nativePaintOrZero,
-                                                 int screenDensity,
-                                                 int bitmapDensity);
-    private static native void native_drawBitmap(long nativeCanvas,
-                                                 long nativeBitmap,
-                                                 Rect src, Rect dst,
-                                                 long nativePaintOrZero,
-                                                 int screenDensity,
-                                                 int bitmapDensity);
+            float srcLeft, float srcTop, float srcRight, float srcBottom,
+            float dstLeft, float dstTop, float dstRight, float dstBottom,
+            long nativePaintOrZero, int screenDensity, int bitmapDensity);
     private static native void native_drawBitmap(long nativeCanvas, int[] colors,
                                                 int offset, int stride, float x,
                                                  float y, int width, int height,
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 8483820..2cb7b03 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -16,8 +16,6 @@
 
 package android.graphics.drawable;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.annotation.NonNull;
@@ -26,6 +24,7 @@
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.LongSparseLongArray;
 import android.util.SparseIntArray;
 import android.util.StateSet;
@@ -62,19 +61,21 @@
  * @attr ref android.R.styleable#DrawableStates_state_pressed
  */
 public class AnimatedStateListDrawable extends StateListDrawable {
+    private static final String LOGTAG = AnimatedStateListDrawable.class.getSimpleName();
+
     private static final String ELEMENT_TRANSITION = "transition";
     private static final String ELEMENT_ITEM = "item";
 
     private AnimatedStateListState mState;
 
-    /** The currently running animation, if any. */
-    private ObjectAnimator mAnim;
+    /** The currently running transition, if any. */
+    private Transition mTransition;
 
-    /** Index to be set after the animation ends. */
-    private int mAnimToIndex = -1;
+    /** Index to be set after the transition ends. */
+    private int mTransitionToIndex = -1;
 
-    /** Index away from which we are animating. */
-    private int mAnimFromIndex = -1;
+    /** Index away from which we are transitioning. */
+    private int mTransitionFromIndex = -1;
 
     private boolean mMutated;
 
@@ -84,20 +85,13 @@
 
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
-        // If we're relying on an Animatable transition, the super method
-        // will handle visibility changes.
         final boolean changed = super.setVisible(visible, restart);
 
-        if (mAnim != null) {
+        if (mTransition != null && (changed || restart)) {
             if (visible) {
-                if (restart) {
-                    mAnim.cancel();
-                    mAnim.start();
-                } else if (changed && mAnim.isPaused()) {
-                    mAnim.resume();
-                }
-            } else if (mAnim.isRunning()) {
-                mAnim.pause();
+                mTransition.start();
+            } else {
+                mTransition.stop();
             }
         }
 
@@ -164,90 +158,193 @@
     }
 
     private boolean selectTransition(int toIndex) {
-        if (toIndex == mAnimToIndex) {
-            // Already animating to that keyframe.
-            return true;
-        }
-
-        if (mAnim != null) {
-            if (toIndex == mAnimToIndex) {
+        final int fromIndex;
+        final Transition currentTransition = mTransition;
+        if (currentTransition != null) {
+            if (toIndex == mTransitionToIndex) {
+                // Already animating to that keyframe.
                 return true;
-            } else if (toIndex == mAnimFromIndex) {
+            } else if (toIndex == mTransitionFromIndex && currentTransition.canReverse()) {
                 // Reverse the current animation.
-                mAnim.reverse();
-                mAnimFromIndex = mAnimToIndex;
-                mAnimToIndex = toIndex;
+                currentTransition.reverse();
+                mTransitionToIndex = mTransitionFromIndex;
+                mTransitionFromIndex = toIndex;
                 return true;
             }
 
+            // Start the next transition from the end of the current one.
+            fromIndex = mTransitionToIndex;
+
             // Changing animation, end the current animation.
-            mAnim.cancel();
-            mAnim = null;
+            currentTransition.stop();
+        } else {
+            fromIndex = getCurrentIndex();
         }
 
         // Reset state.
-        mAnimFromIndex = -1;
-        mAnimToIndex = -1;
+        mTransition = null;
+        mTransitionFromIndex = -1;
+        mTransitionToIndex = -1;
 
         final AnimatedStateListState state = mState;
-        final int fromIndex = getCurrentIndex();
         final int fromId = state.getKeyframeIdAt(fromIndex);
         final int toId = state.getKeyframeIdAt(toIndex);
-
         if (toId == 0 || fromId == 0) {
             // Missing a keyframe ID.
             return false;
         }
 
         final int transitionIndex = state.indexOfTransition(fromId, toId);
-        if (transitionIndex < 0 || !selectDrawable(transitionIndex)) {
+        if (transitionIndex < 0) {
             // Couldn't select a transition.
             return false;
         }
 
+        // This may fail if we're already on the transition, but that's okay!
+        selectDrawable(transitionIndex);
+
+        final Transition transition;
         final Drawable d = getCurrent();
         if (d instanceof AnimationDrawable) {
-            // We can support reverse() here.
-            final boolean reversed = mState.isTransitionReversed(fromId, toId);
-            mAnim = getAnimationDrawableAnimator((AnimationDrawable) d, reversed);
-            mAnim.start();
+            final boolean reversed = state.isTransitionReversed(fromId, toId);
+            transition = new AnimationDrawableTransition((AnimationDrawable) d, reversed);
+        } else if (d instanceof AnimatedVectorDrawable) {
+            final boolean reversed = state.isTransitionReversed(fromId, toId);
+            transition = new AnimatedVectorDrawableTransition((AnimatedVectorDrawable) d, reversed);
         } else if (d instanceof Animatable) {
-            // Let the transition animate itself.
-            ((Animatable) d).start();
+            transition = new AnimatableTransition((Animatable) d);
         } else {
             // We don't know how to animate this transition.
             return false;
         }
 
-        mAnimFromIndex = fromIndex;
-        mAnimToIndex = toIndex;
+        transition.start();
+
+        mTransition = transition;
+        mTransitionFromIndex = fromIndex;
+        mTransitionToIndex = toIndex;
         return true;
     }
 
-    private ObjectAnimator getAnimationDrawableAnimator(@NonNull AnimationDrawable ad,
-            boolean reversed) {
-        final int frameCount = ad.getNumberOfFrames();
-        final int fromFrame = reversed ? frameCount - 1 : 0;
-        final int toFrame = reversed ? 0 : frameCount - 1;
-        final FrameInterpolator interp = new FrameInterpolator(ad, reversed);
-        final ObjectAnimator anim = ObjectAnimator.ofInt(ad, "currentIndex", fromFrame, toFrame);
-        anim.setAutoCancel(true);
-        anim.setDuration(interp.getTotalDuration());
-        anim.addListener(mAnimListener);
-        anim.setInterpolator(interp);
+    private static abstract class Transition {
+        public abstract void start();
+        public abstract void stop();
 
-        return anim;
+        public void reverse() {
+            // Not supported by default.
+        }
+
+        public boolean canReverse() {
+            return false;
+        }
     }
 
+    private static class AnimatableTransition  extends Transition {
+        private final Animatable mA;
+
+        public AnimatableTransition(Animatable a) {
+            mA = a;
+        }
+
+        @Override
+        public void start() {
+            mA.start();
+        }
+
+        @Override
+        public void stop() {
+            mA.stop();
+        }
+    }
+
+
+    private static class AnimationDrawableTransition  extends Transition {
+        private final ObjectAnimator mAnim;
+
+        public AnimationDrawableTransition(AnimationDrawable ad, boolean reversed) {
+            final int frameCount = ad.getNumberOfFrames();
+            final int fromFrame = reversed ? frameCount - 1 : 0;
+            final int toFrame = reversed ? 0 : frameCount - 1;
+            final FrameInterpolator interp = new FrameInterpolator(ad, reversed);
+            final ObjectAnimator anim = ObjectAnimator.ofInt(ad, "currentIndex", fromFrame, toFrame);
+            anim.setAutoCancel(true);
+            anim.setDuration(interp.getTotalDuration());
+            anim.setInterpolator(interp);
+
+            mAnim = anim;
+        }
+
+        @Override
+        public boolean canReverse() {
+            return true;
+        }
+
+        @Override
+        public void start() {
+            mAnim.start();
+        }
+
+        @Override
+        public void reverse() {
+            mAnim.reverse();
+        }
+
+        @Override
+        public void stop() {
+            mAnim.cancel();
+        }
+    }
+
+    private static class AnimatedVectorDrawableTransition  extends Transition {
+        private final AnimatedVectorDrawable mAvd;
+        private final boolean mReversed;
+
+        public AnimatedVectorDrawableTransition(AnimatedVectorDrawable avd, boolean reversed) {
+            mAvd = avd;
+            mReversed = reversed;
+        }
+
+        @Override
+        public boolean canReverse() {
+            return mAvd.canReverse();
+        }
+
+        @Override
+        public void start() {
+            if (mReversed) {
+                reverse();
+            } else {
+                mAvd.start();
+            }
+        }
+
+        @Override
+        public void reverse() {
+            if (canReverse()) {
+                mAvd.reverse();
+            } else {
+                Log.w(LOGTAG, "Reverse() is called on a drawable can't reverse");
+            }
+        }
+
+        @Override
+        public void stop() {
+            mAvd.stop();
+        }
+    }
+
+
     @Override
     public void jumpToCurrentState() {
-        // If we're relying on an Animatable transition, the super method
-        // will handle jumping it to the current state.
         super.jumpToCurrentState();
 
-        if (mAnim != null) {
-            mAnim.end();
-            mAnim = null;
+        if (mTransition != null) {
+            mTransition.stop();
+            mTransition = null;
+
+            selectDrawable(mTransitionToIndex);
+            mTransitionToIndex = -1;
+            mTransitionFromIndex = -1;
         }
     }
 
@@ -404,17 +501,6 @@
         return this;
     }
 
-    private final AnimatorListenerAdapter mAnimListener = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator anim) {
-            selectDrawable(mAnimToIndex);
-
-            mAnimToIndex = -1;
-            mAnimFromIndex = -1;
-            mAnim = null;
-        }
-    };
-
     static class AnimatedStateListState extends StateListState {
         private static final int REVERSE_SHIFT = 32;
         private static final int REVERSE_MASK = 0x1;
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index b5fc628..11c2571 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -16,6 +16,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
+import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
@@ -331,4 +332,38 @@
             animator.pause();
         }
     }
+
+    /**
+     * Reverses ongoing animations or starts pending animations in reverse.
+     * <p>
+     * NOTE: Only works of all animations are ValueAnimators.
+     * @hide
+     */
+    public void reverse() {
+        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.canReverse()) {
+                animator.reverse();
+            } else {
+                Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean canReverse() {
+        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.canReverse()) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index e7a073c..89236ad 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -516,6 +516,11 @@
      */
     public void setHotspotBounds(int left, int top, int right, int bottom) {}
 
+    /** @hide For internal use only. Individual results may vary. */
+    public void getHotspotBounds(Rect outRect) {
+        outRect.set(getBounds());
+    }
+
     /**
      * Whether this drawable requests projection.
      *
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index ed44cde..771322d 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -52,6 +52,7 @@
      */
     private static final boolean DEFAULT_DITHER = true;
     private DrawableContainerState mDrawableContainerState;
+    private Rect mHotspotBounds;
     private Drawable mCurrDrawable;
     private int mAlpha = 0xFF;
 
@@ -273,11 +274,27 @@
 
     @Override
     public void setHotspotBounds(int left, int top, int right, int bottom) {
+        if (mHotspotBounds == null) {
+            mHotspotBounds = new Rect(left, top, bottom, right);
+        } else {
+            mHotspotBounds.set(left, top, bottom, right);
+        }
+
         if (mCurrDrawable != null) {
             mCurrDrawable.setHotspotBounds(left, top, right, bottom);
         }
     }
 
+    /** @hide */
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        if (mHotspotBounds != null) {
+            outRect.set(mHotspotBounds);
+        } else {
+            super.getHotspotBounds(outRect);
+        }
+    }
+
     @Override
     protected boolean onStateChange(int[] state) {
         if (mLastDrawable != null) {
@@ -430,6 +447,12 @@
                 d.setBounds(getBounds());
                 d.setLayoutDirection(getLayoutDirection());
                 d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+
+                final Rect hotspotBounds = mHotspotBounds;
+                if (hotspotBounds != null) {
+                    d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
+                            hotspotBounds.right, hotspotBounds.bottom);
+                }
             }
         } else {
             mCurrDrawable = null;
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index d214a47..6db96b6 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -232,6 +232,12 @@
         mInsetState.mDrawable.setHotspotBounds(left, top, right, bottom);
     }
 
+    /** @hide */
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        mInsetState.mDrawable.getHotspotBounds(outRect);
+    }
+
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
         mInsetState.mDrawable.setVisible(visible, restart);
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index fa68bc5..8d83c74 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -80,6 +80,7 @@
     private int[] mPaddingB;
 
     private final Rect mTmpRect = new Rect();
+    private Rect mHotspotBounds;
     private boolean mMutated;
 
     /**
@@ -630,6 +631,22 @@
         for (int i = 0; i < N; i++) {
             array[i].mDrawable.setHotspotBounds(left, top, right, bottom);
         }
+
+        if (mHotspotBounds == null) {
+            mHotspotBounds = new Rect(left, top, right, bottom);
+        } else {
+            mHotspotBounds.set(left, top, right, bottom);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        if (mHotspotBounds != null) {
+            outRect.set(mHotspotBounds);
+        } else {
+            super.getHotspotBounds(outRect);
+        }
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 4f05313..f955f7c 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -471,6 +471,12 @@
         onHotspotBoundsChanged();
     }
 
+    /** @hide */
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        outRect.set(mHotspotBounds);
+    }
+
     /**
      * Notifies all the animating ripples that the hotspot bounds have changed.
      */
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index c9f541b..7fa1975 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2351,7 +2351,8 @@
         return DrawGlInfo::kStatusDone;
     }
 
-    const Rect& bounds = vertexBuffer.getBounds();
+    Rect bounds(vertexBuffer.getBounds());
+    bounds.translate(translateX, translateY);
     dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
 
     int color = paint->getColor();
diff --git a/media/java/android/media/AudioDevice.java b/media/java/android/media/AudioDevice.java
index 1fd27fe..96d6196 100644
--- a/media/java/android/media/AudioDevice.java
+++ b/media/java/android/media/AudioDevice.java
@@ -66,8 +66,20 @@
         return mConfig.port().address();
     }
 
+    /** @hide */
+    public static int convertDeviceTypeToInternalDevice(int deviceType) {
+        return EXT_TO_INT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE);
+    }
+
+    /** @hide */
+    public static int convertInternalDeviceToDeviceType(int intDevice) {
+        return INT_TO_EXT_DEVICE_MAPPING.get(intDevice, DEVICE_TYPE_UNKNOWN);
+    }
+
     private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING;
 
+    private static final SparseIntArray EXT_TO_INT_DEVICE_MAPPING;
+
     static {
         INT_TO_EXT_DEVICE_MAPPING = new SparseIntArray();
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_EARPIECE, DEVICE_TYPE_BUILTIN_EARPIECE);
@@ -110,6 +122,27 @@
         // not covered here, legacy
         //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
         //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+
+        // privileges mapping to output device
+        EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_EARPIECE, AudioSystem.DEVICE_OUT_EARPIECE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_SPEAKER, AudioSystem.DEVICE_OUT_SPEAKER);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_WIRED_HEADSET, AudioSystem.DEVICE_OUT_WIRED_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_WIRED_HEADPHONES, AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_LINE_ANALOG, AudioSystem.DEVICE_OUT_LINE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_LINE_DIGITAL, AudioSystem.DEVICE_OUT_SPDIF);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BLUETOOTH_SCO, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BLUETOOTH_A2DP, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_HDMI, AudioSystem.DEVICE_OUT_HDMI);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_HDMI_ARC, AudioSystem.DEVICE_OUT_HDMI_ARC);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_USB_DEVICE, AudioSystem.DEVICE_OUT_USB_DEVICE);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_USB_ACCESSORY, AudioSystem.DEVICE_OUT_USB_ACCESSORY);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_DOCK, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_FM, AudioSystem.DEVICE_OUT_FM);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_MIC, AudioSystem.DEVICE_IN_BUILTIN_MIC);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER);
+        EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX);
     }
 }
 
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 025d354..79be108 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -78,9 +78,9 @@
     public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;
     public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;
     public static final int CHANNEL_OUT_BACK_CENTER = 0x400;
-    /** @hide */
+    /** @hide  CANDIDATE FOR PUBLIC API */
     public static final int CHANNEL_OUT_SIDE_LEFT =         0x800;
-    /** @hide */
+    /** @hide  CANDIDATE FOR PUBLIC API */
     public static final int CHANNEL_OUT_SIDE_RIGHT =       0x1000;
     /** @hide */
     public static final int CHANNEL_OUT_TOP_CENTER =       0x2000;
@@ -128,6 +128,35 @@
             CHANNEL_OUT_LOW_FREQUENCY);
     // CHANNEL_OUT_ALL is not yet defined; if added then it should match AUDIO_CHANNEL_OUT_ALL
 
+    /**
+     * @hide
+     * Return the number of channels from an output channel mask
+     * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+     * @return number of channels for the mask
+     */
+    public static int channelCountFromOutChannelMask(int mask) {
+        return Integer.bitCount(mask);
+    }
+    /**
+     * @hide
+     * Return a channel mask ready to be used by native code
+     * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+     * @return a native channel mask
+     */
+    public static int convertChannelOutMaskToNativeMask(int javaMask) {
+        return (javaMask >> 2);
+    }
+
+    /**
+     * @hide
+     * Return a java output channel mask
+     * @param mask a native channel mask
+     * @return a combination of the CHANNEL_OUT_* definitions
+     */
+    public static int convertNativeChannelMaskToOutMask(int nativeMask) {
+        return (nativeMask << 2);
+    }
+
     public static final int CHANNEL_IN_DEFAULT = 1;
     // These directly match native
     public static final int CHANNEL_IN_LEFT = 0x4;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 4abcb81..f84c383 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -136,14 +136,17 @@
  * cycle is necessary.
  *
  * <p> During its life, a codec conceptually exists in one of the following states:
- * Initialized, Configured, Executing, Uninitialized, (omitting transitory states
+ * Initialized, Configured, Executing, Error, Uninitialized, (omitting transitory states
  * between them). When created by one of the factory methods,
  * the codec is in the Initialized state; {@link #configure} brings it to the
  * Configured state; {@link #start} brings it to the Executing state.
  * In the Executing state, decoding or encoding occurs through the buffer queue
  * manipulation described above. The method {@link #stop}
  * returns the codec to the Initialized state, whereupon it may be configured again,
- * and {@link #release} brings the codec to the terminal Uninitialized state.
+ * and {@link #release} brings the codec to the terminal Uninitialized state.  When
+ * a codec error occurs, the codec moves to the Error state.  Use {@link #reset} to
+ * bring the codec back to the Initialized state, or {@link #release} to move it
+ * to the Uninitialized state.
  *
  * <p> The factory methods
  * {@link #createByCodecName},
@@ -170,7 +173,8 @@
  * then resources are temporarily unavailable and the method may be retried at a later time.
  * If both {@link MediaCodec.CodecException#isRecoverable}
  * and {@link MediaCodec.CodecException#isTransient} return false,
- * then the {@link MediaCodec.CodecException} is fatal and the codec must be released.
+ * then the {@link MediaCodec.CodecException} is fatal and the codec must be
+ * {@link #reset reset} or {@link #release released}.
  * Both {@link MediaCodec.CodecException#isRecoverable} and
  * {@link MediaCodec.CodecException#isTransient} do not return true at the same time.
  */
@@ -429,6 +433,23 @@
     }
 
     /**
+     * Returns the codec to its initial (Initialized) state.
+     *
+     * Call this if an {@link MediaCodec.CodecException#isRecoverable unrecoverable}
+     * error has occured to reset the codec to its initial state after creation.
+     *
+     * @throws CodecException if an unrecoverable error has occured and the codec
+     * could not be reset.
+     * @throws IllegalStateException if in the Uninitialized state.
+     */
+    public final void reset() {
+        freeAllTrackedBuffers(); // free buffers first
+        native_reset();
+    }
+
+    private native final void native_reset();
+
+    /**
      * Make sure you call this when you're done to free up any opened
      * component instance instead of relying on the garbage collector
      * to do this for you at some point in the future.
@@ -646,9 +667,10 @@
     /**
      * After filling a range of the input buffer at the specified index
      * submit it to the component. Once an input buffer is queued to
-     * the codec, it MUST not be used until it is later retrieved by
-     * {#getInputBuffer} in response to a {#dequeueInputBuffer}
-     * response.
+     * the codec, it MUST NOT be used until it is later retrieved by
+     * {@link #getInputBuffer} in response to a {@link #dequeueInputBuffer}
+     * return value or a {@link Callback#onInputBufferAvailable}
+     * callback.
      * <p>
      * Many decoders require the actual compressed data stream to be
      * preceded by "codec specific data", i.e. setup data used to initialize
@@ -905,9 +927,10 @@
      * the codec. If you previously specified a surface when configuring this
      * video decoder you can optionally render the buffer.
      *
-     * Once an output buffer is released to the codec, it MUST not
-     * be used until it is later retrieved by {#getOutputBuffer} in
-     * response to a {#dequeueOutputBuffer} response
+     * Once an output buffer is released to the codec, it MUST NOT
+     * be used until it is later retrieved by {@link #getOutputBuffer} in response
+     * to a {@link #dequeueOutputBuffer} return value or a
+     * {@link Callback#onOutputBufferAvailable} callback.
      *
      * @param index The index of a client-owned output buffer previously returned
      *              from a call to {@link #dequeueOutputBuffer}.
@@ -961,9 +984,10 @@
      * </td></tr>
      * </table>
      *
-     * Once an output buffer is released to the codec, it MUST not
-     * be used until it is later retrieved by {#getOutputBuffer} in
-     * response to a {#dequeueOutputBuffer} response
+     * Once an output buffer is released to the codec, it MUST NOT
+     * be used until it is later retrieved by {@link #getOutputBuffer} in response
+     * to a {@link #dequeueOutputBuffer} return value or a
+     * {@link Callback#onOutputBufferAvailable} callback.
      *
      * @param index The index of a client-owned output buffer previously returned
      *              from a call to {@link #dequeueOutputBuffer}.
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index ab65ba0..b15bd69 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -378,6 +378,13 @@
  *     <td>Successful invoke of this method in a valid state does not change
  *         the state. Calling this method in an invalid state transfers the
  *         object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>setAudioAttributes </p></td>
+ *     <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ *          PlaybackCompleted}</p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method does not change the state. In order for the
+ *         target audio attributes type to become effective, this method must be called before
+ *         prepare() or prepareAsync().</p></td></tr>
  * <tr><td>setAudioSessionId </p></td>
  *     <td>{Idle} </p></td>
  *     <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
@@ -787,6 +794,10 @@
      * <p>When done with the MediaPlayer, you should call  {@link #release()},
      * to free the resources. If not released, too many MediaPlayer instances will
      * result in an exception.</p>
+     * <p>Note that since {@link #prepare()} is called automatically in this method,
+     * you cannot change the audio stream type (see {@link #setAudioStreamType(int)}), audio
+     * session ID (see {@link #setAudioSessionId(int)}) or audio attributes
+     * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p>
      *
      * @param context the Context to use
      * @param uri the Uri from which to get the datasource
@@ -802,6 +813,10 @@
      * <p>When done with the MediaPlayer, you should call  {@link #release()},
      * to free the resources. If not released, too many MediaPlayer instances will
      * result in an exception.</p>
+     * <p>Note that since {@link #prepare()} is called automatically in this method,
+     * you cannot change the audio stream type (see {@link #setAudioStreamType(int)}), audio
+     * session ID (see {@link #setAudioSessionId(int)}) or audio attributes
+     * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p>
      *
      * @param context the Context to use
      * @param uri the Uri from which to get the datasource
@@ -809,9 +824,30 @@
      * @return a MediaPlayer object, or null if creation failed
      */
     public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder) {
+        int s = AudioSystem.newAudioSessionId();
+        return create(context, uri, holder, null, s > 0 ? s : 0);
+    }
+
+    /**
+     * Same factory method as {@link #create(Context, Uri, SurfaceHolder)} but that lets you specify
+     * the audio attributes and session ID to be used by the new MediaPlayer instance.
+     * @param context the Context to use
+     * @param uri the Uri from which to get the datasource
+     * @param holder the SurfaceHolder to use for displaying the video, may be null.
+     * @param audioAttributes the {@link AudioAttributes} to be used by the media player.
+     * @param audioSessionId the audio session ID to be used by the media player,
+     *     see {@link AudioManager#allocateAudioSessionId()} to obtain a new session.
+     * @return a MediaPlayer object, or null if creation failed
+     */
+    public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
+            AudioAttributes audioAttributes, int audioSessionId) {
 
         try {
             MediaPlayer mp = new MediaPlayer();
+            final AudioAttributes aa = audioAttributes != null ? audioAttributes :
+                new AudioAttributes.Builder().build();
+            mp.setAudioAttributes(aa);
+            mp.setAudioSessionId(audioSessionId);
             mp.setDataSource(context, uri);
             if (holder != null) {
                 mp.setDisplay(holder);
@@ -840,6 +876,10 @@
      * <p>When done with the MediaPlayer, you should call  {@link #release()},
      * to free the resources. If not released, too many MediaPlayer instances will
      * result in an exception.</p>
+     * <p>Note that since {@link #prepare()} is called automatically in this method,
+     * you cannot change the audio stream type (see {@link #setAudioStreamType(int)}), audio
+     * session ID (see {@link #setAudioSessionId(int)}) or audio attributes
+     * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p>
      *
      * @param context the Context to use
      * @param resid the raw resource id (<var>R.raw.&lt;something></var>) for
@@ -847,11 +887,34 @@
      * @return a MediaPlayer object, or null if creation failed
      */
     public static MediaPlayer create(Context context, int resid) {
+        int s = AudioSystem.newAudioSessionId();
+        return create(context, resid, null, s > 0 ? s : 0);
+    }
+
+    /**
+     * Same factory method as {@link #create(Context, int)} but that lets you specify the audio
+     * attributes and session ID to be used by the new MediaPlayer instance.
+     * @param context the Context to use
+     * @param resid the raw resource id (<var>R.raw.&lt;something></var>) for
+     *              the resource to use as the datasource
+     * @param audioAttributes the {@link AudioAttributes} to be used by the media player.
+     * @param audioSessionId the audio session ID to be used by the media player,
+     *     see {@link AudioManager#allocateAudioSessionId()} to obtain a new session.
+     * @return a MediaPlayer object, or null if creation failed
+     */
+    public static MediaPlayer create(Context context, int resid,
+            AudioAttributes audioAttributes, int audioSessionId) {
         try {
             AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
             if (afd == null) return null;
 
             MediaPlayer mp = new MediaPlayer();
+
+            final AudioAttributes aa = audioAttributes != null ? audioAttributes :
+                new AudioAttributes.Builder().build();
+            mp.setAudioAttributes(aa);
+            mp.setAudioSessionId(audioSessionId);
+
             mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
             afd.close();
             mp.prepare();
@@ -1454,16 +1517,15 @@
     private native boolean setParameter(int key, Parcel value);
 
     /**
-     * @hide
-     * CANDIDATE FOR PUBLIC API
-     * Must call this method before prepare() or
-     * prepareAsync() in order for the audio attributes to become effective
-     * thereafter.
+     * Sets the audio attributes for this MediaPlayer.
+     * See {@link AudioAttributes} for how to build and configure an instance of this class.
+     * You must call this method before {@link #prepare()} or {@link #prepareAsync()} in order
+     * for the audio attributes to become effective thereafter.
      * @param attributes a non-null set of audio attributes
      */
     public void setAudioAttributes(AudioAttributes attributes) throws IllegalArgumentException {
         if (attributes == null) {
-            final String msg = "Cannot set audio attributes to null";
+            final String msg = "Cannot set AudioAttributes to null";
             throw new IllegalArgumentException(msg);
         }
         Parcel pattributes = Parcel.obtain();
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 3336694..7d1de24 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -2249,12 +2249,12 @@
             }
 
             @Override
-            public void onAdjustVolumeBy(final int delta) {
+            public void onAdjustVolume(final int direction) {
                 sStatic.mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         if (mVcb != null) {
-                            mVcb.vcb.onVolumeUpdateRequest(mVcb.route, delta);
+                            mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
                         }
                     }
                 });
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index d151e66..9bda1d4 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -32,14 +32,14 @@
 
     /**
      * The volume control uses relative adjustment via
-     * {@link #onAdjustVolumeBy(int)}. Attempts to set the volume to a specific
+     * {@link #onAdjustVolume(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 #onAdjustVolume(int)} or set directly using
      * {@link #onSetVolumeTo(int)}.
      */
     public static final int VOLUME_CONTROL_ABSOLUTE = 2;
@@ -104,12 +104,13 @@
     }
 
     /**
-     * Override to handle requests to adjust the volume of the current
-     * output.
-     *
-     * @param delta The amount to change the volume
+     * Override to handle requests to adjust the volume of the current output.
+     * Direction will be one of {@link AudioManager#ADJUST_LOWER},
+     * {@link AudioManager#ADJUST_RAISE}, {@link AudioManager#ADJUST_SAME}.
+     * 
+     * @param direction The direction to change the volume in.
      */
-    public void onAdjustVolumeBy(int delta) {
+    public void onAdjustVolume(int direction) {
     }
 
     /**
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 9b381cc..9fa3f50 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -1282,7 +1282,7 @@
     /**
      * @hide
      */
-    public int byteArrayToInt(byte[] valueBuf) {
+    public static int byteArrayToInt(byte[] valueBuf) {
         return byteArrayToInt(valueBuf, 0);
 
     }
@@ -1290,7 +1290,7 @@
     /**
      * @hide
      */
-    public int byteArrayToInt(byte[] valueBuf, int offset) {
+    public static int byteArrayToInt(byte[] valueBuf, int offset) {
         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
         converter.order(ByteOrder.nativeOrder());
         return converter.getInt(offset);
@@ -1300,7 +1300,7 @@
     /**
      * @hide
      */
-    public byte[] intToByteArray(int value) {
+    public static byte[] intToByteArray(int value) {
         ByteBuffer converter = ByteBuffer.allocate(4);
         converter.order(ByteOrder.nativeOrder());
         converter.putInt(value);
@@ -1310,14 +1310,14 @@
     /**
      * @hide
      */
-    public short byteArrayToShort(byte[] valueBuf) {
+    public static short byteArrayToShort(byte[] valueBuf) {
         return byteArrayToShort(valueBuf, 0);
     }
 
     /**
      * @hide
      */
-    public short byteArrayToShort(byte[] valueBuf, int offset) {
+    public static short byteArrayToShort(byte[] valueBuf, int offset) {
         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
         converter.order(ByteOrder.nativeOrder());
         return converter.getShort(offset);
@@ -1327,7 +1327,7 @@
     /**
      * @hide
      */
-    public byte[] shortToByteArray(short value) {
+    public static byte[] shortToByteArray(short value) {
         ByteBuffer converter = ByteBuffer.allocate(2);
         converter.order(ByteOrder.nativeOrder());
         short sValue = (short) value;
@@ -1338,7 +1338,7 @@
     /**
      * @hide
      */
-    public byte[] concatArrays(byte[]... arrays) {
+    public static byte[] concatArrays(byte[]... arrays) {
         int len = 0;
         for (byte[] a : arrays) {
             len += a.length;
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 6b20006..136761b 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -16,9 +16,13 @@
 
 package android.media.audiofx;
 
+import android.media.AudioDevice;
+import android.media.AudioFormat;
 import android.media.audiofx.AudioEffect;
 import android.util.Log;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.StringTokenizer;
 
 
@@ -44,8 +48,10 @@
 public class Virtualizer extends AudioEffect {
 
     private final static String TAG = "Virtualizer";
+    private final static boolean DEBUG = false;
 
-    // These constants must be synchronized with those in frameworks/base/include/media/EffectVirtualizerApi.h
+    // These constants must be synchronized with those in
+    //        system/media/audio_effects/include/audio_effects/effect_virtualizer.h
     /**
      * Is strength parameter supported by virtualizer engine. Parameter ID for getParameter().
      */
@@ -55,6 +61,21 @@
      * {@link android.media.audiofx.Virtualizer.OnParameterChangeListener}
      */
     public static final int PARAM_STRENGTH = 1;
+    /**
+     * @hide
+     * Parameter ID to query the virtual speaker angles for a channel mask / device configuration.
+     */
+    public static final int PARAM_VIRTUAL_SPEAKER_ANGLES = 2;
+    /**
+     * @hide
+     * Parameter ID to force the virtualization mode to be that of a specific device
+     */
+    public static final int PARAM_FORCE_VIRTUALIZATION_MODE = 3;
+    /**
+     * @hide
+     * Parameter ID to query the current virtualization mode.
+     */
+    public static final int PARAM_VIRTUALIZATION_MODE = 4;
 
     /**
      * Indicates if strength parameter is supported by the virtualizer engine
@@ -145,6 +166,223 @@
     }
 
     /**
+     * Checks if a configuration is supported, and query the virtual speaker angles.
+     * @param inputChannelMask
+     * @param deviceType
+     * @param angles if non-null: array in which the angles will be written. If null, no angles
+     *    are returned
+     * @return true if the combination of channel mask and output device type is supported, false
+     *    otherwise
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    private boolean getAnglesInt(int inputChannelMask, int deviceType, int[] angles)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        // parameter check
+        if (inputChannelMask == AudioFormat.CHANNEL_INVALID) {
+            throw (new IllegalArgumentException(
+                    "Virtualizer: illegal CHANNEL_INVALID channel mask"));
+        }
+        int channelMask = inputChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT ?
+                AudioFormat.CHANNEL_OUT_STEREO : inputChannelMask;
+        int nbChannels = AudioFormat.channelCountFromOutChannelMask(channelMask);
+        if ((angles != null) && (angles.length < (nbChannels * 3))) {
+            Log.e(TAG, "Size of array for angles cannot accomodate number of channels in mask ("
+                    + nbChannels + ")");
+            throw (new IllegalArgumentException(
+                    "Virtualizer: array for channel / angle pairs is too small: is " + angles.length
+                    + ", should be " + (nbChannels * 3)));
+        }
+
+        ByteBuffer paramsConverter = ByteBuffer.allocate(3 /* param + mask + device*/ * 4);
+        paramsConverter.order(ByteOrder.nativeOrder());
+        paramsConverter.putInt(PARAM_VIRTUAL_SPEAKER_ANGLES);
+        // convert channel mask to internal native representation
+        paramsConverter.putInt(AudioFormat.convertChannelOutMaskToNativeMask(channelMask));
+        // convert Java device type to internal representation
+        paramsConverter.putInt(AudioDevice.convertDeviceTypeToInternalDevice(deviceType));
+        // allocate an array to store the results
+        byte[] result = new byte[nbChannels * 4/*int to byte*/ * 3/*for mask, azimuth, elevation*/];
+
+        // call into the effect framework
+        int status = getParameter(paramsConverter.array(), result);
+        if (DEBUG) {
+            Log.v(TAG, "getAngles(0x" + Integer.toHexString(inputChannelMask) + ", 0x"
+                    + Integer.toHexString(deviceType) + ") returns " + status);
+        }
+
+        if (status >= 0) {
+            if (angles != null) {
+                // convert and copy the results
+                ByteBuffer resultConverter = ByteBuffer.wrap(result);
+                resultConverter.order(ByteOrder.nativeOrder());
+                for (int i = 0 ; i < nbChannels ; i++) {
+                    // write the channel mask
+                    angles[3 * i] = AudioFormat.convertNativeChannelMaskToOutMask(
+                            resultConverter.getInt((i * 4 * 3)));
+                    // write the azimuth
+                    angles[3 * i + 1] = resultConverter.getInt(i * 4 * 3 + 4);
+                    // write the elevation
+                    angles[3 * i + 2] = resultConverter.getInt(i * 4 * 3 + 8);
+                    if (DEBUG) {
+                        Log.v(TAG, "channel 0x" + Integer.toHexString(angles[3*i]).toUpperCase()
+                                + " at az=" + angles[3*i+1] + "deg"
+                                + " elev="  + angles[3*i+2] + "deg");
+                    }
+                }
+            }
+            return true;
+        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+            // a BAD_VALUE return from getParameter indicates the configuration is not supported
+            // don't throw an exception, just return false
+            return false;
+        } else {
+            // something wrong may have happened
+            checkStatus(status);
+        }
+        // unexpected virtualizer behavior
+        Log.e(TAG, "unexpected status code " + status
+                + " after getParameter(PARAM_VIRTUAL_SPEAKER_ANGLES)");
+        return false;
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Checks if the combination of a channel mask and device type is supported by this virtualizer.
+     * Some virtualizer implementations may only support binaural processing (i.e. only support
+     * headphone output), some may support transaural processing (i.e. for speaker output) for the
+     * built-in speakers. Use this method to query the virtualizer implementation capabilities.
+     * @param inputChannelMask the channel mask of the content to virtualize.
+     * @param deviceType the device type for which virtualization processing is to be performed.
+     *    Valid values are the device types defined in {@link AudioDevice}.
+     * @return true if the combination of channel mask and output device type is supported, false
+     *    otherwise.
+     *    <br>An indication that a certain channel mask is not supported doesn't necessarily mean
+     *    you cannot play content with that channel mask, it more likely implies the content will
+     *    be downmixed before being virtualized. For instance a virtualizer that only supports a
+     *    mask such as {@link AudioFormat#CHANNEL_OUT_STEREO}
+     *    will still be able to process content with a mask of
+     *    {@link AudioFormat#CHANNEL_OUT_5POINT1}, but will downmix the content to stereo first, and
+     *    then will virtualize, as opposed to virtualizing each channel individually.
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    public boolean canVirtualize(int inputChannelMask, int deviceType)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        return getAnglesInt(inputChannelMask, deviceType, null);
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Queries the virtual speaker angles (azimuth and elevation) for a combination of a channel
+     * mask and device type.
+     * If the virtualization configuration (mask and device) is supported (see
+     * {@link #canVirtualize(int, int)}, the array angles will contain upon return the
+     * definition of each virtual speaker and its azimuth and elevation angles relative to the
+     * listener.
+     * <br>Note that in some virtualizer implementations, the angles may be strength-dependent.
+     * @param inputChannelMask the channel mask of the content to virtualize.
+     * @param deviceType the device type for which virtualization processing is to be performed.
+     *    Valid values are the device types defined in {@link AudioDevice}.
+     * @param angles a non-null array whose length is 3 times the number of channels in the channel
+     *    mask.
+     *    If the method indicates the configuration is supported, the array will contain upon return
+     *    triplets of values: for each channel <code>i</code> among the channels of the mask:
+     *    <ul>
+     *      <li>the element at index <code>3*i</code> in the array contains the speaker
+     *          identification (e.g. {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT}),</li>
+     *      <li>the element at index <code>3*i+1</code> contains its corresponding azimuth angle
+     *          expressed in degrees, where 0 is the direction the listener faces, 180 is behind
+     *          the listener, and -90 is to her/his left,</li>
+     *      <li>the element at index <code>3*i+2</code> contains its corresponding elevation angle
+     *          where +90 is directly above the listener, 0 is the horizontal plane, and -90 is
+     *          directly below the listener.</li>
+     * @return true if the combination of channel mask and output device type is supported, false
+     *    otherwise.
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    public boolean getSpeakerAngles(int inputChannelMask, int deviceType, int[] angles)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        if (angles == null) {
+            throw (new IllegalArgumentException(
+                    "Virtualizer: illegal null channel / angle array"));
+        }
+
+        return getAnglesInt(inputChannelMask, deviceType, angles);
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Forces the virtualizer effect to use the processing mode used for the given device type.
+     * The effect must be enabled for the forced mode to be applied.
+     * @param deviceType one of the device types defined in {@link AudioDevice}.
+     *     Use {@link AudioDevice#DEVICE_TYPE_UNKNOWN} to return to the non-forced mode.
+     * @return true if the processing mode for the device type is supported, and it is successfully
+     *     set, or forcing was successfully disabled with {@link AudioDevice#DEVICE_TYPE_UNKNOWN},
+     *     false otherwise.
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
+     * @throws UnsupportedOperationException
+     */
+    public boolean forceVirtualizationMode(int deviceType)
+            throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+        // convert Java device type to internal representation
+        int internalDevice = AudioDevice.convertDeviceTypeToInternalDevice(deviceType);
+
+        int status = setParameter(PARAM_FORCE_VIRTUALIZATION_MODE, internalDevice);
+
+        if (status >= 0) {
+            return true;
+        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+            // a BAD_VALUE return from setParameter indicates the mode can't be forced to that
+            // of this device, don't throw an exception, just return false
+            return false;
+        } else {
+            // something wrong may have happened
+            checkStatus(status);
+        }
+        // unexpected virtualizer behavior
+        Log.e(TAG, "unexpected status code " + status
+                + " after setParameter(PARAM_FORCE_VIRTUALIZATION_MODE)");
+        return false;
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Return the device type which reflects the virtualization mode being used, if any.
+     * @return a device type (as defined in {@link AudioDevice}) which reflects the virtualization
+     *     mode being used.
+     *     If virtualization is not active, the device type will be
+     *     {@link AudioDevice#DEVICE_TYPE_UNKNOWN}. Virtualization may not be active either because
+     *     the effect is not enabled or because the current output device is not compatible with
+     *     this virtualization implementation.
+     */
+    public int getVirtualizationMode() {
+        int[] value = new int[1];
+        int status = getParameter(PARAM_VIRTUALIZATION_MODE, value);
+        if (status >= 0) {
+            return AudioDevice.convertInternalDeviceToDeviceType(value[0]);
+        } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+            return AudioDevice.DEVICE_TYPE_UNKNOWN;
+        } else {
+            // something wrong may have happened
+            checkStatus(status);
+        }
+        // unexpected virtualizer behavior
+        Log.e(TAG, "unexpected status code " + status
+                + " after getParameter(PARAM_VIRTUALIZATION_MODE)");
+        return AudioDevice.DEVICE_TYPE_UNKNOWN;
+    }
+
+    /**
      * The OnParameterChangeListener interface defines a method called by the Virtualizer when a
      * parameter value has changed.
      */
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index e554e27..39391b6 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -39,6 +39,6 @@
     void onRate(in Rating rating);
 
     // These callbacks are for volume handling
-    void onAdjustVolumeBy(int delta);
+    void onAdjustVolume(int direction);
     void onSetVolumeTo(int value);
 }
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index 6cf5ef2..b555220 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -41,7 +41,7 @@
     MediaSessionInfo getSessionInfo();
     long getFlags();
     ParcelableVolumeInfo getVolumeAttributes();
-    void adjustVolumeBy(int delta, int flags);
+    void adjustVolume(int direction, int flags);
     void setVolumeTo(int value, int flags);
 
     IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback);
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index dce84d4..95c2d61 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -31,7 +31,7 @@
     ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
     List<IBinder> getSessions(in ComponentName compName, int userId);
     void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
-    void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags);
+    void dispatchAdjustVolume(int suggestedStream, int delta, int flags);
     void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
             int userId);
     void removeSessionsListener(in IActiveSessionsListener listener);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index cc8b31a..7fedd82 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -66,28 +66,27 @@
 
     private final TransportControls mTransportControls;
 
-    private MediaController(ISessionController sessionBinder) {
+    /**
+     * Call for creating a MediaController directly from a binder. Should only
+     * be used by framework code.
+     *
+     * @hide
+     */
+    public MediaController(ISessionController sessionBinder) {
+        if (sessionBinder == null) {
+            throw new IllegalArgumentException("Session token cannot be null");
+        }
         mSessionBinder = sessionBinder;
         mTransportControls = new TransportControls();
     }
 
     /**
-     * @hide
-     */
-    public static MediaController fromBinder(ISessionController sessionBinder) {
-        return new MediaController(sessionBinder);
-    }
-
-    /**
-     * 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.
+     * Create a new MediaController from a session's token.
      *
-     * @param token The session token to control.
-     * @return A controller for the session or null if inaccessible.
+     * @param token The token for the session.
      */
-    public static MediaController fromToken(@NonNull MediaSession.Token token) {
-        return fromBinder(token.getBinder());
+    public MediaController(@NonNull MediaSession.Token token) {
+        this(token.getBinder());
     }
 
     /**
@@ -235,19 +234,21 @@
     }
 
     /**
-     * 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
+     * Adjust the volume of the stream or output this session is playing on. The
+     * direction must be one of {@link AudioManager#ADJUST_LOWER},
+     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+     * The command will be ignored if the session 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 direction The direction to adjust the volume in.
      * @param flags Any flags to pass with the command.
      */
-    public void adjustVolumeBy(int delta, int flags) {
+    public void adjustVolume(int direction, int flags) {
         try {
-            mSessionBinder.adjustVolumeBy(delta, flags);
+            mSessionBinder.adjustVolume(direction, flags);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
         }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 34997bd..4841360 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
@@ -37,10 +38,13 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.telephony.DctConstants.Activity;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,8 +63,9 @@
  * 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
- * commands a {@link TransportControlsCallback} must be set with
+ * set with {@link #addCallback(Callback)} and {@link #setActive(boolean)
+ * setActive(true)} must be called. To receive transport control commands a
+ * {@link TransportControlsCallback} must be set with
  * {@link #addTransportControlsCallback}.
  * <p>
  * When an app is finished performing playback it must call {@link #release()}
@@ -119,18 +124,45 @@
     private boolean mActive = false;
 
     /**
+     * Creates a new session. The session will automatically be registered with
+     * the system but will not be published until {@link #setActive(boolean)
+     * setActive(true)} is called. You must call {@link #release()} when
+     * finished with the session.
+     *
+     * @param context The context to use to create the session.
+     * @param tag A short name for debugging purposes.
+     */
+    public MediaSession(@NonNull Context context, @NonNull String tag) {
+        this(context, tag, UserHandle.myUserId());
+    }
+
+    /**
+     * Creates a new session as the specified user. To create a session as a
+     * user other than your own you must hold the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+     * permission.
+     *
+     * @param context The context to use to create the session.
+     * @param tag A short name for debugging purposes.
+     * @param userId The user id to create the session as.
      * @hide
      */
-    public MediaSession(ISession binder, CallbackStub cbStub) {
-        mBinder = binder;
-        mCbStub = cbStub;
-        ISessionController controllerBinder = null;
-        try {
-            controllerBinder = mBinder.getController();
-        } catch (RemoteException e) {
-            throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
+    public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
+        if (context == null) {
+            throw new IllegalArgumentException("context cannot be null.");
         }
-        mSessionToken = new Token(controllerBinder);
+        if (TextUtils.isEmpty(tag)) {
+            throw new IllegalArgumentException("tag cannot be null or empty");
+        }
+        mCbStub = new CallbackStub();
+        MediaSessionManager manager = (MediaSessionManager) context
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+        try {
+            mBinder = manager.createSession(mCbStub, tag, userId);
+            mSessionToken = new Token(mBinder.getController());
+        } catch (RemoteException e) {
+            throw new RuntimeException("Remote error creating session.", e);
+        }
     }
 
     /**
@@ -837,11 +869,11 @@
         }
 
         @Override
-        public void onAdjustVolumeBy(int delta) {
+        public void onAdjustVolume(int direction) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 if (session.mVolumeProvider != null) {
-                    session.mVolumeProvider.onAdjustVolumeBy(delta);
+                    session.mVolumeProvider.onAdjustVolume(direction);
                 }
             }
         }
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 11f7720..da1a6ed 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -46,6 +47,7 @@
     private static final Object sLock = new Object();
     private static MediaSessionLegacyHelper sInstance;
 
+    private Context mContext;
     private MediaSessionManager mSessionManager;
     private Handler mHandler = new Handler(Looper.getMainLooper());
     // The legacy APIs use PendingIntents to register/unregister media button
@@ -54,6 +56,7 @@
             = new ArrayMap<PendingIntent, SessionHolder>();
 
     private MediaSessionLegacyHelper(Context context) {
+        mContext = context;
         mSessionManager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
     }
@@ -206,13 +209,13 @@
                 }
             }
 
-            mSessionManager.dispatchAdjustVolumeBy(AudioManager.USE_DEFAULT_STREAM_TYPE,
+            mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
                     direction, flags);
         }
     }
 
     public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
-        mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+        mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
         if (DEBUG) {
             Log.d(TAG, "dispatched volume adjustment");
         }
@@ -225,6 +228,9 @@
             return;
         }
         SessionHolder holder = getHolder(pi, true);
+        if (holder == null) {
+            return;
+        }
         if (holder.mRccListener != null) {
             if (holder.mRccListener == listener) {
                 if (DEBUG) {
@@ -270,6 +276,9 @@
             return;
         }
         SessionHolder holder = getHolder(pi, true);
+        if (holder == null) {
+            return;
+        }
         if (holder.mMediaButtonListener != null) {
             // Already have this listener registered, but update it anyway as
             // the extras may have changed.
@@ -316,7 +325,8 @@
     private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
         SessionHolder holder = mSessions.get(pi);
         if (holder == null && createIfMissing) {
-            MediaSession session = mSessionManager.createSession(TAG);
+            MediaSession session;
+            session = new MediaSession(mContext, TAG);
             session.setActive(true);
             holder = new SessionHolder(session, pi);
             mSessions.put(pi, holder);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c477406..824b397 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.media.AudioManager;
 import android.media.IRemoteVolumeController;
 import android.media.session.ISessionManager;
 import android.os.IBinder;
@@ -64,42 +65,15 @@
     }
 
     /**
-     * Creates a new session.
+     * Create a new session in the system and get the binder for it.
      *
      * @param tag A short name for debugging purposes.
-     * @return A {@link MediaSession} for the new session.
-     */
-    public @NonNull MediaSession createSession(@NonNull String tag) {
-        return createSessionAsUser(tag, UserHandle.myUserId());
-    }
-
-    /**
-     * Creates a new session as the specified user. To create a session as a
-     * user other than your own you must hold the
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
-     * permission.
-     *
-     * @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 The binder object from the system
      * @hide
      */
-    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
-                    .createSession(mContext.getPackageName(), cbStub, tag, userId), cbStub);
-            cbStub.setMediaSession(session);
-
-            return session;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to create session: ", e);
-            return null;
-        }
+    public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
+            @NonNull String tag, int userId) throws RemoteException {
+        return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
     }
 
     /**
@@ -142,7 +116,7 @@
             List<IBinder> binders = mService.getSessions(notificationListener, userId);
             int size = binders.size();
             for (int i = 0; i < size; i++) {
-                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                MediaController controller = new MediaController(ISessionController.Stub
                         .asInterface(binders.get(i)));
                 controllers.add(controller);
             }
@@ -256,17 +230,19 @@
 
     /**
      * Dispatch an adjust volume request to the system. It will be sent to the
-     * most relevant audio stream or media session.
+     * most relevant audio stream or media session. The direction must be one of
+     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+     * {@link AudioManager#ADJUST_SAME}.
      *
      * @param suggestedStream The stream to fall back to if there isn't a
      *            relevant stream
-     * @param delta The amount to adjust the volume by.
+     * @param direction The direction to adjust volume in.
      * @param flags Any flags to include with the volume change.
      * @hide
      */
-    public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+    public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
         try {
-            mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+            mService.dispatchAdjustVolume(suggestedStream, direction, flags);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to send adjust volume.", e);
         }
@@ -295,7 +271,7 @@
                 ArrayList<MediaController> controllers = new ArrayList<MediaController>();
                 int size = tokens.size();
                 for (int i = 0; i < size; i++) {
-                    controllers.add(MediaController.fromToken(tokens.get(i)));
+                    controllers.add(new MediaController(tokens.get(i)));
                 }
                 SessionListener.this.onActiveSessionsChanged(controllers);
             }
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
new file mode 100644
index 0000000..905b0bd
--- /dev/null
+++ b/media/java/android/media/tv/TvContentRating.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.media.tv;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class representing a TV content rating.
+ */
+public class TvContentRating {
+    private static final String TAG = "TvContentRating";
+
+    private static final int RATING_PREFIX_LENGTH = 10;
+    private static final String PREFIX_RATING_US = "RATING_US_";
+    private static final String PREFIX_SUBRATING_US = "SUBRATING_US_";
+
+    /**
+     * Rating constant for TV-Y from the TV Parental Guidelines system in US. This program is
+     * designed to be appropriate for all children.
+     */
+    public static final String RATING_US_TV_Y = PREFIX_RATING_US + "TV_Y";
+    /**
+     * Rating constant for TV-Y7 from the TV Parental Guidelines system in US. This program is
+     * designed for children age 7 and above.
+     */
+    public static final String RATING_US_TV_Y7 = PREFIX_RATING_US + "TV_Y7";
+    /**
+     * Rating constant for TV-G from the TV Parental Guidelines system in US. Most parents would
+     * find this program suitable for all ages.
+     */
+    public static final String RATING_US_TV_G = PREFIX_RATING_US + "TV_G";
+    /**
+     * Rating constant for TV-PG from the TV Parental Guidelines system in US. This program contains
+     * material that parents may find unsuitable for younger children.
+     */
+    public static final String RATING_US_TV_PG = PREFIX_RATING_US + "TV_PG";
+    /**
+     * Rating constant for TV-14 from the TV Parental Guidelines system in US. This program contains
+     * some material that many parents would find unsuitable for children under 14 years of age.
+     */
+    public static final String RATING_US_TV_14 = PREFIX_RATING_US + "TV_14";
+    /**
+     * Rating constant for TV-MA from the TV Parental Guidelines system in US. This program is
+     * specifically designed to be viewed by adults and therefore may be unsuitable for children
+     * under 17.
+     */
+    public static final String RATING_US_TV_MA = PREFIX_RATING_US + "TV_MA";
+
+    /**
+     * Sub-rating constant for D (Suggestive dialogue) from the TV Parental Guidelines system in US.
+     */
+    public static final String SUBRATING_US_D = PREFIX_SUBRATING_US + "D";
+    /**
+     * Sub-rating constant for L (Coarse language) from the TV Parental Guidelines system in US.
+     */
+    public static final String SUBRATING_US_L = PREFIX_SUBRATING_US + "L";
+    /**
+     * Sub-rating constant for S (Sexual content) from the TV Parental Guidelines system in US.
+     */
+    public static final String SUBRATING_US_S = PREFIX_SUBRATING_US + "S";
+    /**
+     * Sub-rating constant for V (Violence) from the TV Parental Guidelines system in US.
+     */
+    public static final String SUBRATING_US_V = PREFIX_SUBRATING_US + "V";
+    /**
+     * Sub-rating constant for FV (Fantasy violence) from the TV Parental Guidelines system in US.
+     */
+    public static final String SUBRATING_US_FV = PREFIX_SUBRATING_US + "FV";
+
+    private static final String PREFIX_RATING_KR = "RATING_KR_";
+
+    /**
+     * Rating constant for 'ALL' from the South Korean television rating system. This rating is for
+     * programming that is appropriate for all ages.
+     */
+    public static final String RATING_KR_ALL = PREFIX_RATING_KR + "ALL";
+    /**
+     * Rating constant for '7' from the South Korean television rating system. This rating is for
+     * programming that may contain material inappropriate for children younger than 7, and parental
+     * discretion should be used.
+     */
+    public static final String RATING_KR_7 = PREFIX_RATING_KR + "7";
+    /**
+     * Rating constant for '12' from the South Korean television rating system. This rating is for
+     * programs that may deemed inappropriate for those younger than 12, and parental discretion
+     * should be used.
+     */
+    public static final String RATING_KR_12 = PREFIX_RATING_KR + "12";
+    /**
+     * Rating constant for '15' from the South Korean television rating system. This rating is for
+     * programs that contain material that may be inappropriate for children under 15, and that
+     * parental discretion should be used.
+     */
+    public static final String RATING_KR_15 = PREFIX_RATING_KR + "15";
+    /**
+     * Rating constant for '19' from the South Korean television rating system. This rating is for
+     * programs that are intended for adults only. 19-rated programming cannot air during the hours
+     * of 7:00AM to 9:00AM, and 1:00PM to 10:00PM.
+     */
+    public static final String RATING_KR_19 = PREFIX_RATING_KR + "19";
+
+    private static final String DELIMITER = "/";
+
+    // A mapping from two-letter country code (ISO 3166-1 alpha-2) to its rating-to-sub-ratings map.
+    // This is used for validating the builder parameters.
+    private static final Map<String, Map<String, String[]>> sRatings
+            = new HashMap<String, Map<String, String[]>>();
+
+    static {
+        Map<String, String[]> usRatings = new HashMap<String, String[]>();
+        usRatings.put(RATING_US_TV_Y, null);
+        usRatings.put(RATING_US_TV_Y7, new String[] { SUBRATING_US_FV });
+        usRatings.put(RATING_US_TV_G, null);
+        usRatings.put(RATING_US_TV_PG, new String[] {
+                SUBRATING_US_D, SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
+        usRatings.put(RATING_US_TV_14, new String[] {
+                SUBRATING_US_D, SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
+        usRatings.put(RATING_US_TV_MA, new String[] {
+                SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
+        sRatings.put(PREFIX_RATING_US, usRatings);
+
+        Map<String, String[]> krRatings = new HashMap<String, String[]>();
+        krRatings.put(RATING_KR_ALL, null);
+        krRatings.put(RATING_KR_7, null);
+        krRatings.put(RATING_KR_12, null);
+        krRatings.put(RATING_KR_15, null);
+        krRatings.put(RATING_KR_19, null);
+        sRatings.put(PREFIX_RATING_KR, krRatings);
+    }
+
+    private final String mRating;
+    private final String[] mSubRatings;
+
+    /**
+     * Constructs a TvContentRating object from a given rating constant.
+     *
+     * @param rating The rating constant defined in this class.
+     */
+    public TvContentRating(String rating) {
+        mRating = rating;
+        mSubRatings = null;
+    }
+
+    /**
+     * Constructs a TvContentRating object from a given rating and sub-rating constants.
+     *
+     * @param rating The rating constant defined in this class.
+     * @param subRatings The String array of sub-rating constants defined in this class.
+     */
+    public TvContentRating(String rating, String[] subRatings) {
+        mRating = rating;
+        mSubRatings = subRatings;
+        if (TextUtils.isEmpty(mRating)) {
+            throw new IllegalArgumentException("rating cannot be null");
+        }
+        String prefix = "";
+        if (mRating.length() > RATING_PREFIX_LENGTH) {
+            prefix = mRating.substring(0, RATING_PREFIX_LENGTH);
+        }
+        Map<String, String[]> ratings = sRatings.get(prefix);
+        if (ratings != null) {
+            if (!ratings.keySet().contains(mRating)) {
+                Log.w(TAG, "Unknown rating: " + mRating);
+            } else if (mSubRatings != null) {
+                String[] validSubRatings = ratings.get(mRating);
+                if (validSubRatings == null) {
+                    Log.w(TAG, "Invalid subratings: " + mSubRatings);
+                } else {
+                    List<String> validSubRatingList = Arrays.asList(subRatings);
+                    for (String sr : mSubRatings) {
+                        if (!validSubRatingList.contains(sr)) {
+                            Log.w(TAG, "Invalid subrating: " + sr);
+                            break;
+                        }
+                    }
+                }
+            }
+        } else {
+            Log.w(TAG, "Rating undefined for " + mRating);
+        }
+    }
+
+    /**
+     * Recovers a TvContentRating from a String that was previously created with
+     * {@link #flattenToString}.
+     *
+     * @param ratingString The String that was returned by flattenToString().
+     * @return a new TvContentRating containing the rating and sub-ratings information was encoded
+     *         in {@code ratingString}.
+     * @see #flattenToString
+     */
+    public static TvContentRating unflattenFromString(String ratingString) {
+        if (TextUtils.isEmpty(ratingString)) {
+            throw new IllegalArgumentException("Empty rating string");
+        }
+        String[] strs = ratingString.split(DELIMITER);
+        if (strs.length < 1) {
+            throw new IllegalArgumentException("Invalid rating string: " + ratingString);
+        }
+        if (strs.length > 1) {
+            String[] subRatings = new String[strs.length - 1];
+            System.arraycopy(strs, 1, subRatings, 0, subRatings.length);
+            return new TvContentRating(strs[0], subRatings);
+        }
+        return new TvContentRating(strs[0]);
+    }
+
+    /**
+     * @return a String that unambiguously describes both the rating and sub-rating information
+     *         contained in the TvContentRating. You can later recover the TvContentRating from this
+     *         string through {@link #unflattenFromString}.
+     * @see #unflattenFromString
+     */
+    public String flattenToString() {
+        StringBuffer ratingStr = new StringBuffer();
+        ratingStr.append(mRating);
+        if (mSubRatings != null) {
+            for (String subRating : mSubRatings) {
+                ratingStr.append(DELIMITER);
+                ratingStr.append(subRating);
+            }
+        }
+        return ratingStr.toString();
+    }
+}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 46aeb1d..ffb6850 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentUris;
+import android.media.tv.TvContract.Programs;
 import android.net.Uri;
 import android.provider.BaseColumns;
 import android.util.ArraySet;
@@ -913,6 +914,24 @@
         public static final String COLUMN_AUDIO_LANGUAGE = "audio_language";
 
         /**
+         * The comma-separated content ratings of this TV program.
+         * <p>
+         * This is used to describe the content rating(s) of this program. Each comma-separated
+         * content rating sub-string should be generated by calling
+         * {@link TvContentRating#flattenToString}. Note that in most cases the program content is
+         * rated by a single rating system, thus resulting in a corresponding single sub-string that
+         * does not require comma separation and multiple sub-strings appear only when the program
+         * content is rated by two or more content rating systems. If any of those ratings is
+         * specified as "blocked rating" in the user's parental control settings, the TV input
+         * service should block the current content and wait for the signal that it is okay to
+         * unblock.
+         * </p><p>
+         * Type: TEXT
+         * </p>
+         */
+        public static final String COLUMN_CONTENT_RATING = "content_rating";
+
+        /**
          * The URI for the poster art of this TV program.
          * <p>
          * Can be empty.
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index b7294b8..04ff098 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -197,6 +197,10 @@
     return mCodec->flush();
 }
 
+status_t JMediaCodec::reset() {
+    return mCodec->reset();
+}
+
 status_t JMediaCodec::queueInputBuffer(
         size_t index,
         size_t offset, size_t size, int64_t timeUs, uint32_t flags,
@@ -854,6 +858,26 @@
     throwExceptionAsNecessary(env, err);
 }
 
+static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_reset");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        // should never be here
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->reset();
+    if (err != OK) {
+        // treat all errors as fatal for now, though resource not available
+        // errors could be treated as transient.
+        err = 0x80000000;
+    }
+    throwExceptionAsNecessary(env, err);
+}
+
 static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) {
     ALOGV("android_media_MediaCodec_flush");
 
@@ -1398,6 +1422,8 @@
 static JNINativeMethod gMethods[] = {
     { "native_release", "()V", (void *)android_media_MediaCodec_release },
 
+    { "native_reset", "()V", (void *)android_media_MediaCodec_reset },
+
     { "native_setCallback",
       "(Landroid/media/MediaCodec$Callback;)V",
       (void *)android_media_MediaCodec_native_setCallback },
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 2e650e3..dbccb0f 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -56,6 +56,7 @@
 
     status_t start();
     status_t stop();
+    status_t reset();
 
     status_t flush();
 
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 260ee39..9a62864 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -69,7 +69,7 @@
         <activity
             android:name=".ui.SelectPrinterActivity"
             android:label="@string/all_printers_label"
-            android:theme="@style/SelectPrinterActivityTheme"
+            android:theme="@android:style/Theme.Material.Settings"
             android:exported="false">
         </activity>
 
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_check_circle.png b/packages/PrintSpooler/res/drawable-hdpi/ic_check_circle.png
new file mode 100644
index 0000000..4ad5417
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_check_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
new file mode 100644
index 0000000..b6a5eb5
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png
deleted file mode 100644
index d2e5408..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png
new file mode 100644
index 0000000..4e36bd2
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.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
deleted file mode 100644
index f4c4b0c..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_remove_circle.png b/packages/PrintSpooler/res/drawable-hdpi/ic_remove_circle.png
new file mode 100644
index 0000000..ef053b6
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_remove_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_check_circle.png b/packages/PrintSpooler/res/drawable-mdpi/ic_check_circle.png
new file mode 100644
index 0000000..f66065a
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_check_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
new file mode 100644
index 0000000..428a946
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
new file mode 100644
index 0000000..fbbd094
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_remove_circle.png b/packages/PrintSpooler/res/drawable-mdpi/ic_remove_circle.png
new file mode 100644
index 0000000..7e044ac
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_remove_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_check_circle.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_check_circle.png
new file mode 100644
index 0000000..d8ea4d29
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_check_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png
new file mode 100644
index 0000000..6161c20
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.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
deleted file mode 100644
index f0074275..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png
new file mode 100644
index 0000000..3a89805
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.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
deleted file mode 100644
index 43debb3..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_remove_circle.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_remove_circle.png
new file mode 100644
index 0000000..622989c
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_remove_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_check_circle.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_check_circle.png
new file mode 100644
index 0000000..ac36eba
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_check_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png
new file mode 100644
index 0000000..52a52d9
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.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
deleted file mode 100644
index 39bc2ba..0000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png
new file mode 100644
index 0000000..15e6abd
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.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
deleted file mode 100644
index 664f3f2..0000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_remove_circle.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_remove_circle.png
new file mode 100644
index 0000000..303ccfb
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_remove_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_check_circle.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_check_circle.png
new file mode 100644
index 0000000..1737f0a
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_check_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png
new file mode 100644
index 0000000..46811a1
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.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
deleted file mode 100644
index fe9c539..0000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png
new file mode 100644
index 0000000..141f28b
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.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
deleted file mode 100644
index 18d075c..0000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_remove_circle.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_remove_circle.png
new file mode 100644
index 0000000..e9c6252
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_remove_circle.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_less.xml b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
index b0c7d51..6f1ece1 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_less.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
@@ -20,7 +20,7 @@
     <item
         android:state_checked="true">
         <bitmap
-            android:src="@drawable/ic_expand_less_24dp"
+            android:src="@drawable/ic_expand_less"
             android:tint="?android:attr/colorControlActivated">
         </bitmap>
     </item>
@@ -28,14 +28,14 @@
     <item
         android:state_pressed="true">
         <bitmap
-            android:src="@drawable/ic_expand_less_24dp"
+            android:src="@drawable/ic_expand_less"
             android:tint="?android:attr/colorControlActivated">
         </bitmap>
     </item>
 
     <item>
         <bitmap
-            android:src="@drawable/ic_expand_less_24dp"
+            android:src="@drawable/ic_expand_less"
             android:tint="?android:attr/colorControlNormal">
         </bitmap>
     </item>
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_more.xml b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
index b809c25..8d71452 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_more.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
@@ -20,7 +20,7 @@
     <item
         android:state_checked="true">
         <bitmap
-            android:src="@drawable/ic_expand_more_24dp"
+            android:src="@drawable/ic_expand_more"
             android:tint="?android:attr/colorControlActivated">
         </bitmap>
     </item>
@@ -28,14 +28,14 @@
     <item
         android:state_pressed="true">
         <bitmap
-            android:src="@drawable/ic_expand_more_24dp"
+            android:src="@drawable/ic_expand_more"
             android:tint="?android:attr/colorControlActivated">
         </bitmap>
     </item>
 
     <item>
         <bitmap
-            android:src="@drawable/ic_expand_more_24dp"
+            android:src="@drawable/ic_expand_more"
             android:tint="?android:attr/colorControlNormal">
         </bitmap>
     </item>
diff --git a/packages/PrintSpooler/res/drawable/ic_search.xml b/packages/PrintSpooler/res/drawable/ic_search.xml
new file mode 100644
index 0000000..991fa38b
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_search.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="@*android:drawable/ic_menu_search"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item
+        android:state_pressed="true">
+        <bitmap
+            android:src="@*android:drawable/ic_menu_search"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item>
+        <bitmap
+            android:src="@*android:drawable/ic_menu_search"
+            android:tint="?android:attr/colorControlNormal">
+        </bitmap>
+    </item>
+
+</selector>
diff --git a/packages/PrintSpooler/res/drawable/page_selector_background.xml b/packages/PrintSpooler/res/drawable/page_selector_background.xml
new file mode 100644
index 0000000..7f1da31
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/page_selector_background.xml
@@ -0,0 +1,36 @@
+<?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:enterFadeDuration="@android:integer/config_shortAnimTime"
+    android:exitFadeDuration="@android:integer/config_shortAnimTime">
+
+    <item
+        android:state_selected="true">
+        <bitmap
+            android:src="@drawable/ic_check_circle"
+            android:tint="@color/promoted_action_background_color">
+        </bitmap>
+    </item>
+
+    <item>
+        <bitmap
+            android:src="@drawable/ic_remove_circle"
+            android:tint="@color/promoted_action_background_color">
+        </bitmap>
+    </item>
+
+</selector>
diff --git a/packages/PrintSpooler/res/drawable/print_button_background.xml b/packages/PrintSpooler/res/drawable/print_button_background.xml
index 7b9aea5..aec8474 100644
--- a/packages/PrintSpooler/res/drawable/print_button_background.xml
+++ b/packages/PrintSpooler/res/drawable/print_button_background.xml
@@ -18,7 +18,7 @@
     android:shape="oval">
 
     <solid
-        android:color="#FF00E5FF">
+        android:color="@color/promoted_action_background_color">
     </solid>
 
     <size
diff --git a/packages/PrintSpooler/res/layout/preview_page.xml b/packages/PrintSpooler/res/layout/preview_page.xml
index 0e314d1..509a1d2 100644
--- a/packages/PrintSpooler/res/layout/preview_page.xml
+++ b/packages/PrintSpooler/res/layout/preview_page.xml
@@ -31,7 +31,7 @@
     <RelativeLayout
         android:id="@+id/page_footer"
         android:layout_width="fill_parent"
-        android:layout_height="?android:attr/listPreferredItemHeightSmall"
+        android:layout_height="32dip"
         android:background="@*android:color/material_grey_500"
         android:orientation="horizontal">
 
@@ -40,18 +40,19 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_centerInParent="true"
-            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorPrimary">
         </TextView>
 
-        <CheckBox
+        <ImageView
             android:id="@+id/page_selector"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginRight="8dip"
             android:layout_alignParentEnd="true"
-            android:layout_centerVertical="true">
-        </CheckBox>
+            android:layout_centerVertical="true"
+            android:background="@drawable/page_selector_background">
+        </ImageView>
 
     </RelativeLayout>
 
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 8896a7b..3905646 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -29,7 +29,7 @@
         android:layout_height="wrap_content"
         android:padding="16dip"
         android:elevation="@dimen/preview_controls_elevation"
-        android:background="?android:attr/colorForegroundInverse">
+        android:background="?android:attr/colorPrimary">
 
         <Spinner
             android:id="@+id/destination_spinner"
@@ -51,7 +51,7 @@
         android:paddingEnd="16dip"
         android:orientation="horizontal"
         android:elevation="@dimen/preview_controls_elevation"
-        android:background="?android:attr/colorForegroundInverse">
+        android:background="?android:attr/colorPrimary">
 
         <TextView
             android:layout_width="wrap_content"
diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml
index 2da0714..ef6044a 100644
--- a/packages/PrintSpooler/res/layout/print_activity_controls.xml
+++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml
@@ -22,7 +22,7 @@
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:elevation="@dimen/preview_controls_elevation"
-    android:background="?android:attr/colorForegroundInverse">
+    android:background="?android:attr/colorPrimary">
 
         <LinearLayout
          android:id="@+id/draggable_content"
@@ -61,6 +61,8 @@
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      style="?android:attr/editTextStyle"
+                     android:singleLine="true"
+                     android:ellipsize="end"
                      android:inputType="numberDecimal">
                  </view>
 
@@ -88,8 +90,7 @@
                  <Spinner
                      android:id="@+id/paper_size_spinner"
                      android:layout_width="fill_parent"
-                     android:layout_height="wrap_content"
-                     style="@style/PrintOptionSpinnerStyle">
+                     android:layout_height="wrap_content">
                  </Spinner>
 
              </LinearLayout>
@@ -116,8 +117,7 @@
                  <Spinner
                      android:id="@+id/color_spinner"
                      android:layout_width="fill_parent"
-                     android:layout_height="wrap_content"
-                     style="@style/PrintOptionSpinnerStyle">
+                     android:layout_height="wrap_content">
                  </Spinner>
 
              </LinearLayout>
@@ -144,8 +144,7 @@
                  <Spinner
                      android:id="@+id/orientation_spinner"
                      android:layout_width="fill_parent"
-                     android:layout_height="wrap_content"
-                     style="@style/PrintOptionSpinnerStyle">
+                     android:layout_height="wrap_content">
                  </Spinner>
 
              </LinearLayout>
@@ -160,21 +159,19 @@
                  <!-- 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">
+                     android:text="@string/label_pages">
                  </TextView>
 
                  <Spinner
                      android:id="@+id/range_options_spinner"
                      android:layout_width="fill_parent"
-                     android:layout_height="wrap_content"
-                     style="@style/PrintOptionSpinnerStyle">
+                     android:layout_height="wrap_content">
                  </Spinner>
 
              </LinearLayout>
@@ -207,7 +204,8 @@
                      android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_gravity="bottom|fill_horizontal"
-                     style="@style/PrintOptionEditTextStyle"
+                     android:singleLine="true"
+                     android:ellipsize="end"
                      android:visibility="visible"
                      android:inputType="textNoSuggestions">
                  </view>
diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml
index 1f5efbc..7bc144a 100644
--- a/packages/PrintSpooler/res/layout/printer_list_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_list_item.xml
@@ -49,7 +49,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:gravity="top|start"
-            android:textColor="?android:attr/textColorSecondary"
+            android:textColor="?android:attr/textColorPrimary"
             android:duplicateParentState="true">
         </TextView>
 
@@ -62,7 +62,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:visibility="gone"
-            android:textColor="?android:attr/textColorSecondary"
+            android:textColor="?android:attr/textColorPrimary"
             android:duplicateParentState="true">
         </TextView>
 
diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
index 1fb221a..14403a1 100644
--- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
@@ -17,8 +17,6 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
-    android:paddingStart="8dip"
-    android:paddingEnd="8dip"
     android:minHeight="?android:attr/listPreferredItemHeightSmall"
     android:orientation="vertical"
     android:gravity="start|center_vertical">
@@ -27,12 +25,13 @@
         android:id="@+id/title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        style="?android:attr/spinnerDropDownItemStyle"
         android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorPrimary"
         android:singleLine="true"
         android:ellipsize="end"
         android:textIsSelectable="false"
         android:gravity="top|left"
-        android:textColor="?android:attr/textColorPrimary"
         android:duplicateParentState="true">
     </TextView>
 
@@ -40,12 +39,13 @@
         android:id="@+id/subtitle"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        style="?android:attr/spinnerDropDownItemStyle"
         android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorPrimary"
         android:singleLine="true"
         android:ellipsize="end"
         android:textIsSelectable="false"
         android:visibility="gone"
-        android:textColor="?android:attr/textColorPrimary"
         android:duplicateParentState="true">
     </TextView>
 
diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
index ee62f9f..8da5769 100644
--- a/packages/PrintSpooler/res/menu/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -19,7 +19,7 @@
     <item
         android:id="@+id/action_search"
         android:title="@string/search"
-        android:icon="@*android:drawable/ic_menu_search_holo_light"
+        android:icon="@*android:drawable/ic_search"
         android:actionViewClass="android.widget.SearchView"
         android:showAsAction="ifRoom|collapseActionView"
         android:alphabeticShortcut="f"
diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml
index 677fda7..de74a41 100644
--- a/packages/PrintSpooler/res/values/colors.xml
+++ b/packages/PrintSpooler/res/values/colors.xml
@@ -22,4 +22,6 @@
 
     <color name="print_preview_background_color">#F2F1F2</color>
 
+    <color name="promoted_action_background_color">#FF80CBC4</color>
+
 </resources>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index dd90bec..5b7fda3 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -44,7 +44,13 @@
     <string name="label_orientation">Orientation</string>
 
     <!-- Label of the page selection widget. [CHAR LIMIT=20] -->
-    <string name="label_pages">Pages (<xliff:g id="page_count" example="5">%1$s</xliff:g>)</string>
+    <string name="label_pages">Pages</string>
+
+    <!-- Template for the all pages option in the page selection widget. [CHAR LIMIT=20] -->
+    <string name="template_all_pages">All <xliff:g id="page_count" example="100">%1$s</xliff:g></string>
+
+    <!-- Template for the page range option in the page selection widget. [CHAR LIMIT=20] -->
+    <string name="template_page_range">Range of <xliff:g id="page_count" example="100">%1$s</xliff:g></string>
 
     <!-- Page range exmple used as a hint of how to specify such. [CHAR LIMIT=20] -->
     <string name="pages_range_example">e.g. 1&#8212;5,8,11&#8212;13</string>
@@ -58,9 +64,6 @@
     <!-- Title of the message that the printing application crashed. [CHAR LIMIT=50] -->
     <string name="printing_app_crashed">Printing app crashed</string>
 
-    <!-- Title if the number of pages in a printed document is unknown. [CHAR LIMIT=20] -->
-    <string name="page_count_unknown">Pages</string>
-
     <!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] -->
     <string name="generating_print_job">Generating print job</string>
 
@@ -174,14 +177,6 @@
         <item>Landscape</item>
     </string-array>
 
-    <!-- Page options labels. -->
-    <string-array name="page_options_labels">
-        <!-- Page range option label: Print all pages [CHAR LIMIT=30] -->
-        <item>All</item>
-        <!-- Page range option label: Print a page range [CHAR LIMIT=30] -->
-        <item>Range</item>
-    </string-array>
-
     <!-- Permissions -->
 
     <!-- Title of an application permission, listed so the user can choose whether they want
diff --git a/packages/PrintSpooler/res/values/styles.xml b/packages/PrintSpooler/res/values/styles.xml
deleted file mode 100644
index 9637847..0000000
--- a/packages/PrintSpooler/res/values/styles.xml
+++ /dev/null
@@ -1,31 +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.
--->
-
-<resources>
-
-    <style name="PrintOptionSpinnerStyle">
-        <item name="android:paddingTop">0dip</item>
-        <item name="android:paddingBottom">0dip</item>
-        <item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
-    </style>
-
-    <style name="PrintOptionEditTextStyle">
-
-         <item name="android:singleLine">true</item>
-         <item name="android:ellipsize">end</item>
-    </style>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index e1e6c44..7d0da14 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -16,19 +16,16 @@
 
 <resources>
 
-    <style name="PrintActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+    <style name="PrintActivity" parent="@android:style/Theme.Material">
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
         <item name="android:backgroundDimEnabled">false</item>
-    </style>
-
-    <style name="SelectPrinterActivityTheme" parent="@android:style/Theme.DeviceDefault.Light">
-        <item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
-    </style>
-
-    <style name="SelectPrinterActivityActionBarStyle" parent="@android:style/Widget.DeviceDefault.Light.ActionBar">
-        <item name="android:displayOptions">showTitle</item>
+        <item name="android:colorPrimary">@*android:color/material_blue_grey_900</item>
+        <item name="android:colorPrimaryDark">@*android:color/material_blue_grey_950</item>
+        <item name="android:colorAccent">@*android:color/material_dark_teal_A400</item>
     </style>
 
 </resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 094edf8..30808ba 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -334,22 +334,22 @@
         content.init(provider, mMediaSize, mMinMargins);
 
 
-        CheckBox checkbox = (CheckBox) page.findViewById(R.id.page_selector);
-        checkbox.setTag(myHolder);
+        View pageSelector = page.findViewById(R.id.page_selector);
+        pageSelector.setTag(myHolder);
         if (pageCount > 1) {
-            checkbox.setOnClickListener(mPageClickListener);
-            checkbox.setVisibility(View.VISIBLE);
+            pageSelector.setOnClickListener(mPageClickListener);
+            pageSelector.setVisibility(View.VISIBLE);
         } else {
-            checkbox.setOnClickListener(null);
-            checkbox.setVisibility(View.GONE);
+            pageSelector.setOnClickListener(null);
+            pageSelector.setVisibility(View.GONE);
         }
 
         if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) {
-            checkbox.setChecked(true);
+            pageSelector.setSelected(true);
             page.setTranslationZ(mSelectedPageElevation);
             page.setAlpha(mSelectedPageAlpha);
         } else {
-            checkbox.setChecked(false);
+            pageSelector.setSelected(false);
             page.setTranslationZ(mUnselectedPageElevation);
             page.setAlpha(mUnselectedPageAlpha);
         }
@@ -767,15 +767,15 @@
             MyViewHolder holder = (MyViewHolder) page.getTag();
             final int pageInAdapter = holder.mPageInAdapter;
             final int pageInDocument = computePageIndexInDocument(pageInAdapter);
-            CheckBox pageSelector = (CheckBox) page.findViewById(R.id.page_selector);
+            View pageSelector = page.findViewById(R.id.page_selector);
             if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) {
                 mConfirmedPagesInDocument.put(pageInDocument, null);
-                pageSelector.setChecked(true);
+                pageSelector.setSelected(true);
                 page.animate().translationZ(mSelectedPageElevation)
                         .alpha(mSelectedPageAlpha);
             } else {
                 mConfirmedPagesInDocument.remove(pageInDocument);
-                pageSelector.setChecked(false);
+                pageSelector.setSelected(false);
                 page.animate().translationZ(mUnselectedPageElevation)
                         .alpha(mUnselectedPageAlpha);
             }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 5e646ff..a01e45c 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -157,7 +157,6 @@
 
     private EditText mCopiesEditText;
 
-    private TextView mPageRangeOptionsTitle;
     private TextView mPageRangeTitle;
     private EditText mPageRangeEditText;
 
@@ -196,6 +195,8 @@
 
     private String mCallingPackageName;
 
+    private int mCurrentPageCount;
+
     private int mState;
 
     private int mUiState = UI_STATE_PREVIEW;
@@ -996,19 +997,10 @@
         // 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);
+        updatePageRangeOptions(PrintDocumentInfo.PAGE_COUNT_UNKNOWN);
 
         // Page range
         mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
@@ -1265,22 +1257,23 @@
                     mPageRangeTitle.setVisibility(View.INVISIBLE);
                 }
             }
-            String title = (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
-                    ? getString(R.string.label_pages, String.valueOf(pageCount))
-                    : getString(R.string.page_count_unknown);
-            mPageRangeOptionsTitle.setText(title);
         } else {
             if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
                 mRangeOptionsSpinner.setSelection(0);
                 mPageRangeEditText.setText("");
             }
             mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeOptionsTitle.setText(getString(R.string.page_count_unknown));
             mPageRangeEditText.setEnabled(false);
             mPageRangeEditText.setVisibility(View.INVISIBLE);
             mPageRangeTitle.setVisibility(View.INVISIBLE);
         }
 
+        final int newPageCount = getAdjustedPageCount(info);
+        if (newPageCount != mCurrentPageCount) {
+            mCurrentPageCount = newPageCount;
+            updatePageRangeOptions(newPageCount);
+        }
+
         // Advanced print options
         ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
         if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
@@ -1320,6 +1313,27 @@
         }
     }
 
+    private void updatePageRangeOptions(int pageCount) {
+        ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
+                (ArrayAdapter) mRangeOptionsSpinner.getAdapter();
+        rangeOptionsSpinnerAdapter.clear();
+
+        final int[] rangeOptionsValues = getResources().getIntArray(
+                R.array.page_options_values);
+
+        String pageCountLabel = (pageCount > 0) ? String.valueOf(pageCount) : "";
+        String[] rangeOptionsLabels = new String[] {
+            getString(R.string.template_all_pages, pageCountLabel),
+            getString(R.string.template_page_range, pageCountLabel)
+        };
+
+        final int rangeOptionsCount = rangeOptionsLabels.length;
+        for (int i = 0; i < rangeOptionsCount; i++) {
+            rangeOptionsSpinnerAdapter.add(new SpinnerItem<>(
+                    rangeOptionsValues[i], rangeOptionsLabels[i]));
+        }
+    }
+
     private PageRange[] computeSelectedPages() {
         if (hasErrors()) {
             return null;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 555aa97..efb030e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -22,6 +22,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
 import com.android.printspooler.R;
 
 /**
@@ -301,6 +302,7 @@
             mSummaryContent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             mDraggableContent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             mMoreOptionsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            ensureImeClosedAndInputFocusCleared();
         }
         if ((mDragProgress > 0 && progress == 0)
                 || (mDragProgress < 1.0f && progress == 1.0f)) {
@@ -351,6 +353,18 @@
         }
     }
 
+    private void ensureImeClosedAndInputFocusCleared() {
+        View focus = findFocus();
+        if (focus != null) {
+            InputMethodManager imm = (InputMethodManager) mContext.getSystemService(
+                    Context.INPUT_METHOD_SERVICE);
+            if (imm.isActive(focus)) {
+                imm.hideSoftInputFromWindow(getWindowToken(), 0);
+            }
+            focus.clearFocus();
+        }
+    }
+
     private final class DragCallbacks extends ViewDragHelper.Callback {
         @Override
         public boolean tryCaptureView(View child, int pointerId) {
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
index eedae9f..1d6df61 100644
--- a/packages/SystemUI/res/layout/qs_user_detail.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -16,9 +16,14 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.qs.tiles.UserDetail
+<!-- GridView -->
+<com.android.systemui.qs.tiles.UserDetailView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    <include layout="@layout/user_switcher_host" />
-</com.android.systemui.qs.tiles.UserDetail>
\ No newline at end of file
+        android:layout_height="match_parent"
+        android:verticalSpacing="4dp"
+        android:horizontalSpacing="4dp"
+        android:numColumns="3"
+        android:listSelector="@drawable/ripple_drawable">
+
+</com.android.systemui.qs.tiles.UserDetailView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
new file mode 100644
index 0000000..00b3645
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.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
+  -->
+
+<com.android.systemui.qs.tiles.UserDetailItemView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:systemui="http://schemas.android.com/apk/res-auto"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:gravity="top|center_horizontal"
+        android:paddingTop="16dp"
+        android:paddingBottom="20dp">
+
+    <com.android.systemui.statusbar.phone.UserAvatarView
+            android:id="@+id/user_picture"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginBottom="12dp"
+            systemui:frameWidth="2dp"
+            systemui:activeFrameColor="@color/current_user_border_color"/>
+
+    <TextView
+            android:id="@+id/user_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="14sp"
+            android:text="@string/guest_nickname"/>
+
+</com.android.systemui.qs.tiles.UserDetailItemView>
\ 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 7e8bfd32..3e4c1f6 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -28,7 +28,7 @@
         android:layout_height="@dimen/recents_task_bar_height"
         android:layout_gravity="top|center_horizontal"
         android:background="@color/recents_task_bar_default_background_color">
-        <ImageView
+        <com.android.systemui.recents.views.FixedSizeImageView
             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"
@@ -51,7 +51,7 @@
             android:maxLines="2"
             android:ellipsize="marquee"
             android:fadingEdge="horizontal" />
-        <ImageView
+        <com.android.systemui.recents.views.FixedSizeImageView
             android:id="@+id/dismiss_task"
             android:layout_width="48dp"
             android:layout_height="48dp"
diff --git a/packages/SystemUI/res/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml
deleted file mode 100644
index c1626c6..0000000
--- a/packages/SystemUI/res/layout/user_switcher_host.xml
+++ /dev/null
@@ -1,31 +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
-  -->
-
-<!-- FrameLayout -->
-<com.android.systemui.settings.UserSwitcherHostView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <ListView android:id="@android:id/list"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                tools:listitem="@layout/user_switcher_item"/>
-
-</com.android.systemui.settings.UserSwitcherHostView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/user_switcher_item.xml b/packages/SystemUI/res/layout/user_switcher_item.xml
deleted file mode 100644
index 8df2f5a..0000000
--- a/packages/SystemUI/res/layout/user_switcher_item.xml
+++ /dev/null
@@ -1,49 +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
-  -->
-
-<LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="64dp"
-        android:orientation="horizontal"
-        android:gravity="center_vertical"
-        tools:context=".settings.UserSwitcherDialog">
-    <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:layout_marginStart="4dp"
-            android:id="@+id/user_picture"
-            tools:src="@drawable/dessert_zombiegingerbread"/>
-    <TextView
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:id="@+id/user_name"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:padding="8dp"
-            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/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3d53f9c..adab243 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -45,7 +45,7 @@
     <color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
     <color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
     <color name="data_usage_graph_warning">#FFFFFFFF</color>
-    <color name="status_bar_clock_color">#33FFFFFF</color>
+    <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>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d4feccd..751c889 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -691,4 +691,7 @@
 
     <!-- Battery level for expanded quick settings [CHAR LIMIT=2] -->
     <string name="battery_level_template"><xliff:g id="level" example="45">%d</xliff:g>%%</string>
+
+    <!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
+    <string name="notification_hidden_text">Contents hidden</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7da6c22..708d3e8 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -270,4 +270,10 @@
         <item name="android:windowEnterAnimation">@*android:anim/dock_top_enter</item>
         <item name="android:windowExitAnimation">@*android:anim/dock_top_exit</item>
     </style>
+
+    <style name="TextAppearance.StatusBar.Material.EventContent.Parenthetical"
+           parent="@*android:style/TextAppearance.StatusBar.Material.EventContent">
+        <item name="android:textStyle">italic</item>
+        <item name="android:textColor">#60000000</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 5f09cbd..4901f40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -108,6 +108,9 @@
         mHost = host;
     }
 
+    public QSTileHost getHost() {
+        return mHost;
+    }
 
     public void updateResources() {
         final Resources res = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
deleted file mode 100644
index a9a2724..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
+++ /dev/null
@@ -1,78 +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.qs.tiles;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-
-import android.content.Context;
-import android.content.Intent;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-/**
- * Quick settings detail view for user switching.
- */
-public class UserDetail extends FrameLayout {
-
-    static final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
-
-    public UserDetail(Context context) {
-        this(context, null);
-    }
-
-    public UserDetail(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public UserDetail(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public UserDetail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    public static QSTile.DetailAdapter USER_DETAIL_ADAPTER = new QSTile.DetailAdapter() {
-        @Override
-        public int getTitle() {
-            return R.string.quick_settings_user_title;
-        }
-
-        @Override
-        public Boolean getToggleState() {
-            return null;
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            return LayoutInflater.from(context).inflate(R.layout.qs_user_detail, parent, false);
-        }
-
-        @Override
-        public Intent getSettingsIntent() {
-            return USER_SETTINGS_INTENT;
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
new file mode 100644
index 0000000..d765aab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.qs.tiles;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Displays one user in the {@link UserDetailView} view.
+ */
+public class UserDetailItemView extends LinearLayout {
+
+    private UserAvatarView mAvatar;
+    private TextView mName;
+
+    public UserDetailItemView(Context context) {
+        this(context, null);
+    }
+
+    public UserDetailItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public static UserDetailItemView convertOrInflate(Context context, View convertView,
+            ViewGroup root) {
+        if (!(convertView instanceof UserDetailItemView)) {
+            convertView = LayoutInflater.from(context).inflate(
+                    R.layout.qs_user_detail_item, root, false);
+        }
+        return (UserDetailItemView) convertView;
+    }
+
+    public void bind(String name, Bitmap picture) {
+        mName.setText(name);
+        mAvatar.setBitmap(picture);
+    }
+
+    public void bind(String name, Drawable picture) {
+        mName.setText(name);
+        mAvatar.setDrawable(picture);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mAvatar = (UserAvatarView) findViewById(R.id.user_picture);
+        mName = (TextView) findViewById(R.id.user_name);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
new file mode 100644
index 0000000..ec5f28c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.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 com.android.systemui.qs.tiles;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.GridView;
+
+/**
+ * Quick settings detail view for user switching.
+ */
+public class UserDetailView extends GridView {
+
+    public UserDetailView(Context context) {
+        this(context, null);
+    }
+
+    public UserDetailView(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.gridViewStyle);
+    }
+
+    public UserDetailView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserDetailView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                UserSwitcherController.UserRecord tag =
+                        (UserSwitcherController.UserRecord) view.getTag();
+                ((Adapter)getAdapter()).switchTo(tag);
+            }
+        });
+    }
+
+    public static UserDetailView inflate(Context context, ViewGroup parent, boolean attach) {
+        return (UserDetailView) LayoutInflater.from(context).inflate(
+                R.layout.qs_user_detail, parent, attach);
+    }
+
+    public void createAndSetAdapter(UserSwitcherController controller) {
+        setAdapter(new Adapter(mContext, controller));
+    }
+
+    public static class Adapter extends UserSwitcherController.BaseUserAdapter {
+
+        private Context mContext;
+
+        public Adapter(Context context, UserSwitcherController controller) {
+            super(controller);
+            mContext = context;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            UserSwitcherController.UserRecord item = getItem(position);
+            UserDetailItemView v = UserDetailItemView.convertOrInflate(
+                    mContext, convertView, parent);
+            String name;
+            if (item.isGuest) {
+                name = mContext.getString(
+                        item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
+            } else {
+                name = item.info.name;
+            }
+            if (item.picture == null) {
+                v.bind(name, mContext.getDrawable(R.drawable.ic_account_circle));
+            } else {
+                v.bind(name, item.picture);
+            }
+            v.setActivated(item.isCurrent);
+            v.setTag(item);
+            return v;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 2f1c1c4..31011ae 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -25,7 +25,6 @@
 
 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
@@ -33,7 +32,7 @@
  */
 public class RecentsPackageMonitor extends PackageMonitor {
     public interface PackageCallbacks {
-        public void onComponentRemoved(Set<ComponentName> cns);
+        public void onComponentRemoved(HashSet<ComponentName> cns);
     }
 
     PackageCallbacks mCb;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index e3bcff0..13fbe64 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -204,8 +204,8 @@
                 removeGroup(group);
             }
             // Update the lock-to-app state
-            Task newFrontMostTask = getFrontMostTask();
             t.canLockToTask = false;
+            Task newFrontMostTask = getFrontMostTask();
             if (newFrontMostTask != null) {
                 newFrontMostTask.canLockToTask = true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
new file mode 100644
index 0000000..3adee0ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when
+ * setting the image to Null.
+ */
+public class FixedSizeImageView extends ImageView {
+
+    int mFixedWidth;
+    int mFixedHeight;
+    boolean mAllowRelayout = true;
+    boolean mAllowInvalidate = true;
+
+    public FixedSizeImageView(Context context) {
+        this(context, null);
+    }
+
+    public FixedSizeImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mFixedWidth = getMeasuredWidth();
+        mFixedHeight = getMeasuredHeight();
+    }
+
+    @Override
+    public void requestLayout() {
+        if (mAllowRelayout) {
+            super.requestLayout();
+        }
+    }
+
+    @Override
+    public void invalidate() {
+        if (mAllowInvalidate) {
+            super.invalidate();
+        }
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        if (drawable == null || (mFixedWidth > 0 && mFixedHeight > 0)) {
+            mAllowRelayout = false;
+            mAllowInvalidate = false;
+        }
+        super.setImageDrawable(drawable);
+        mAllowRelayout = true;
+        mAllowInvalidate = true;
+    }
+}
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 7bb6144..78a99e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -45,7 +45,7 @@
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
-import java.util.Set;
+import java.util.HashSet;
 
 /**
  * This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -456,9 +456,11 @@
                 } else {
                     // Launch the activity anew with the desired animation
                     Intent i = new Intent(task.key.baseIntent);
-                    i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
-                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME
-                            | Intent.FLAG_ACTIVITY_NEW_TASK);
+                    i.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+                    if ((i.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == 0) {
+                        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    }
                     try {
                         UserHandle taskUser = new UserHandle(task.userId);
                         if (launchOpts != null) {
@@ -551,7 +553,7 @@
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
-    public void onComponentRemoved(Set<ComponentName> cns) {
+    public void onComponentRemoved(HashSet<ComponentName> cns) {
         // Propagate this event down to each task stack view
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
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 7b52163..0b35f59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -23,9 +23,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.SystemClock;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -45,7 +44,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Set;
+import java.util.HashSet;
 
 
 /* The visual representation of a task stack view */
@@ -83,6 +82,7 @@
     int mFocusedTaskIndex = -1;
     OverScroller mScroller;
     ObjectAnimator mScrollAnimator;
+    boolean mEnableStackClipping = true;
 
     // Optimizations
     ReferenceCountedTrigger mHwLayersTrigger;
@@ -169,6 +169,7 @@
     void requestSynchronizeStackViewsWithModel(int duration) {
         if (!mStackViewsDirty) {
             invalidate(mStackAlgorithm.mStackRect);
+            mStackViewsDirty = true;
         }
         if (mAwaitingFirstLayout) {
             // Skip the animation if we are awaiting first layout
@@ -176,7 +177,6 @@
         } else {
             mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
         }
-        mStackViewsDirty = true;
     }
 
     /** Returns a mapping of child view to Task. */
@@ -338,7 +338,7 @@
     /** Updates the clip for each of the task views. */
     void clipTaskViews() {
         // Update the clip on each task child
-        if (Constants.DebugFlags.App.EnableTaskStackClipping) {
+        if (Constants.DebugFlags.App.EnableTaskStackClipping && mEnableStackClipping) {
             int childCount = getChildCount();
             for (int i = 0; i < childCount - 1; i++) {
                 TaskView tv = (TaskView) getChildAt(i);
@@ -360,10 +360,12 @@
                     // stacked and we can make assumptions about the visibility of the this
                     // task relative to the ones in front of it.
                     if (nextTv != null) {
-                        // XXX: Can hash the visible rects for this run
+                        // We calculate the bottom clip independent of the footer (since we animate
+                        // that)
+                        int scaledMaxFooterHeight = (int) (tv.getMaxFooterHeight() * tv.getScaleX());
                         tv.getHitRect(mTmpRect);
                         nextTv.getHitRect(mTmpRect2);
-                        clipBottom = (mTmpRect.bottom - mTmpRect2.top);
+                        clipBottom = (mTmpRect.bottom - scaledMaxFooterHeight - mTmpRect2.top);
                     }
                 }
                 tv.setClipFromBottom(clipBottom);
@@ -376,6 +378,18 @@
         }
     }
 
+    /** Enables/Disables clipping of the tasks in the stack. */
+    void setStackClippingEnabled(boolean stackClippingEnabled) {
+        if (!stackClippingEnabled) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                TaskView tv = (TaskView) getChildAt(i);
+                tv.setClipFromBottom(0);
+            }
+        }
+        mEnableStackClipping = stackClippingEnabled;
+    }
+
     /** Sets the current stack scroll */
     public void setStackScroll(int value) {
         mStackScroll = value;
@@ -614,7 +628,6 @@
     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()) {
@@ -668,7 +681,7 @@
             TaskView t = (TaskView) getChildAt(i);
             t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
-                            mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
+                            t.getMaxFooterHeight(), MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -687,7 +700,7 @@
             TaskView t = (TaskView) getChildAt(i);
             t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
                     mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
-                    mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
+                    mStackAlgorithm.mTaskRect.height() + t.getMaxFooterHeight());
         }
 
         if (mAwaitingFirstLayout) {
@@ -1056,7 +1069,7 @@
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
-    public void onComponentRemoved(Set<ComponentName> cns) {
+    public void onComponentRemoved(HashSet<ComponentName> cns) {
         // 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--) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 9c48896..65407a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -146,7 +146,8 @@
         transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
 
         // Set the alphas
-        transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+        // transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+        transformOut.dismissAlpha = 1f;
 
         // Update the rect and visibility
         transformOut.rect.set(mTaskRect);
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 636746d..08a25f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
@@ -21,12 +21,11 @@
 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 {
+public class TaskThumbnailView extends FixedSizeImageView {
 
     Task mTask;
 
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 7e30047..199d3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -495,7 +495,7 @@
         mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
     }
 
-    /** Sets the stubbed state of this task view.
+    /** Sets the stubbed state of this task view. */
     void setStubState(boolean isStub) {
         if (!mIsStub && isStub) {
             // This is now a stub task view, so clip to the bar height, hide the thumbnail
@@ -508,7 +508,7 @@
             mThumbnailView.setVisibility(View.VISIBLE);
         }
         mIsStub = isStub;
-    } */
+    }
 
     /**
      * Returns whether this view should be clipped, or any views below should clip against this
@@ -549,9 +549,19 @@
         return mFooterHeight;
     }
 
+    /** Gets the max footer height. */
+    public int getMaxFooterHeight() {
+        return mMaxFooterHeight;
+    }
+
     /** Animates the footer into and out of view. */
     public void animateFooterVisibility(boolean visible, int duration, int delay) {
-        if (!mTask.canLockToTask) return;
+        if (!mTask.canLockToTask) {
+            if (mLockToAppButtonView.getVisibility() == View.VISIBLE) {
+                mLockToAppButtonView.setVisibility(View.INVISIBLE);
+            }
+            return;
+        }
         if (mMaxFooterHeight <= 0) return;
 
         if (mFooterAnimator != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
deleted file mode 100644
index a5c5862..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
+++ /dev/null
@@ -1,273 +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.settings;
-
-import com.android.systemui.R;
-
-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;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManagerGlobal;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A quick and dirty view to show a user switcher.
- */
-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);
-
-        if (isInEditMode()) {
-            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) {
-        this(context, attrs, com.android.internal.R.attr.listViewStyle);
-    }
-
-    public UserSwitcherHostView(Context context) {
-        this(context, null);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mListView = (ListView) findViewById(android.R.id.list);
-        mListView.setAdapter(mAdapter);
-        mListView.setOnItemClickListener(this);
-        refreshUsers();
-    }
-
-    @Override
-    public void onItemClick(AdapterView<?> l, View v, int position, long 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);
-            finish();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't switch user.", e);
-        }
-    }
-
-    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();
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_UP) {
-            finish();
-        }
-        return true;
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-        // A gross hack to get rid of the switcher when the shade is collapsed.
-        if (visibility != VISIBLE) {
-            finish();
-        }
-    }
-
-    public void setFinishRunnable(Runnable finishRunnable) {
-        mFinishRunnable = finishRunnable;
-    }
-
-    public void refreshUsers() {
-        mUserInfo.clear();
-        mGuestUser = null;
-        List<UserInfo> users = mUserManager.getUsers(true);
-        for (UserInfo user : users) {
-            if (user.isGuest()) {
-                mGuestUser = user;
-            } else if (!user.isManagedProfile()) {
-                mUserInfo.add(user);
-            }
-        }
-        mAdapter.notifyDataSetChanged();
-    }
-
-    private class Adapter extends BaseAdapter {
-
-        @Override
-        public int getCount() {
-            return mUserInfo.size() + (mGuestUserEnabled ? 1 : 0);
-        }
-
-        @Override
-        public UserInfo getItem(int position) {
-            if (position < mUserInfo.size()) {
-                return mUserInfo.get(position);
-            } else {
-                return mGuestUser;
-            }
-        }
-
-        @Override
-        public long getItemId(int position) {
-            if (position < mUserInfo.size()) {
-                return getItem(position).serialNumber;
-            } else {
-                return mGuestUser != null ? mGuestUser.serialNumber : -1;
-            }
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null || (!(convertView.getTag() instanceof ViewHolder))) {
-                convertView = createView(parent);
-            }
-            ViewHolder h = (ViewHolder) convertView.getTag();
-            bindView(h, getItem(position));
-            return convertView;
-        }
-
-        private View createView(ViewGroup parent) {
-            View v = LayoutInflater.from(getContext()).inflate(
-                    R.layout.user_switcher_item, parent, false);
-            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) {
-            if (item != null) {
-                h.name.setText(item.name);
-                h.picture.setImageBitmap(circularClip(mUserManager.getUserIcon(item.id)));
-                h.delete.setVisibility(item.isGuest() ? View.VISIBLE : View.GONE);
-                h.delete.setOnClickListener(UserSwitcherHostView.this);
-                if (item.isGuest()) {
-                    h.picture.setImageResource(R.drawable.ic_account_circle);
-                }
-            } else {
-                h.name.setText(R.string.guest_new_guest);
-                h.picture.setImageResource(R.drawable.ic_account_circle);
-                h.delete.setVisibility(View.GONE);
-            }
-        }
-
-        private Bitmap circularClip(Bitmap input) {
-            if (input == null) {
-                return null;
-            }
-            Bitmap output = Bitmap.createBitmap(input.getWidth(),
-                    input.getHeight(), Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(output);
-            final Paint paint = new Paint();
-            paint.setShader(new BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
-            paint.setAntiAlias(true);
-            canvas.drawCircle(input.getWidth() / 2, input.getHeight() / 2, input.getWidth() / 2,
-                    paint);
-            return output;
-        }
-
-        class ViewHolder {
-            TextView name;
-            ImageView picture;
-            ImageView delete;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 9101c8d..56ea359 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -62,8 +62,10 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewStub;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
+import android.widget.DateTimeView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupMenu;
@@ -1075,14 +1077,28 @@
                 }
             }
 
+            final View privateTime = contentViewLocal.findViewById(com.android.internal.R.id.time);
+            if (privateTime != null && privateTime.getVisibility() == View.VISIBLE) {
+                final View timeStub = publicViewLocal.findViewById(com.android.internal.R.id.time);
+                timeStub.setVisibility(View.VISIBLE);
+                final DateTimeView dateTimeView = (DateTimeView)
+                        publicViewLocal.findViewById(com.android.internal.R.id.time);
+                dateTimeView.setTime(entry.notification.getNotification().when);
+            }
+
             final TextView text = (TextView) publicViewLocal.findViewById(
-                    com.android.internal.R.id.text);
-            text.setText("Unlock your device to see this notification.");
+                com.android.internal.R.id.text);
+            if (text != null) {
+                text.setText(R.string.notification_hidden_text);
+                text.setTextAppearance(mContext,
+                        R.style.TextAppearance_StatusBar_Material_EventContent_Parenthetical);
+            }
 
             entry.autoRedacted = true;
-            // TODO: fill out "time" as well
         }
 
+        row.setClearable(sbn.isClearable());
+
         row.setDrawingCacheEnabled(true);
 
         if (MULTIUSER_DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 280bade..3410834 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -53,6 +53,7 @@
     private NotificationContentView mPrivateLayout;
     private int mMaxExpandHeight;
     private View mVetoButton;
+    private boolean mClearable;
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -166,6 +167,23 @@
     }
 
     /**
+     * @return Can the underlying notification be cleared?
+     */
+    public boolean isClearable() {
+        return mClearable;
+    }
+
+    /**
+     * Set whether the notification can be cleared.
+     *
+     * @param clearable
+     */
+    public void setClearable(boolean clearable) {
+        mClearable = clearable;
+        updateVetoButton();
+    }
+
+    /**
      * Apply an expansion state to the layout.
      */
     public void applyExpansionToLayout() {
@@ -223,6 +241,13 @@
         // TODO: animation?
         mPublicLayout.setVisibility(show ? View.VISIBLE : View.GONE);
         mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE);
+
+        updateVetoButton();
+    }
+
+    private void updateVetoButton() {
+        // public versions cannot be dismissed
+        mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
     }
 
     public int getMaxExpandHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index d32ad50..688c0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -26,7 +26,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.tiles.UserDetail;
+import com.android.systemui.qs.tiles.UserDetailView;
 
 /**
  * Container for image of the multi user switcher (tappable).
@@ -53,7 +53,8 @@
     public void onClick(View v) {
         final UserManager um = UserManager.get(getContext());
         if (um.isUserSwitcherEnabled()) {
-            mQsPanel.showDetailAdapter(true, UserDetail.USER_DETAIL_ADAPTER);
+            mQsPanel.showDetailAdapter(true,
+                    mQsPanel.getHost().getUserSwitcherController().userDetailAdapter);
         } else {
             Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
                     getContext(), v, ContactsContract.Profile.CONTENT_URI,
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 2c43161..0fa4810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -127,6 +127,7 @@
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
@@ -194,6 +195,7 @@
     VolumeComponent mVolumeComponent;
     KeyguardUserSwitcher mKeyguardUserSwitcher;
     FlashlightController mFlashlightController;
+    UserSwitcherController mUserSwitcherController;
 
     int mNaturalBarHeight = -1;
     int mIconSize = -1;
@@ -691,6 +693,7 @@
         }
 
         mFlashlightController = new FlashlightController(mContext);
+        mUserSwitcherController = new UserSwitcherController(mContext);
 
         // Set up the quick settings tile panel
         mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
@@ -698,7 +701,8 @@
             final QSTileHost qsh = new QSTileHost(mContext, this,
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, null /*tethering*/,
-                    mCastController, mVolumeComponent, mFlashlightController);
+                    mCastController, mVolumeComponent, mFlashlightController,
+                    mUserSwitcherController);
             mQSPanel.setHost(qsh);
             for (QSTile<?> tile : qsh.getTiles()) {
                 mQSPanel.addTile(tile);
@@ -2326,6 +2330,9 @@
         if (mCastController != null) {
             mCastController.dump(fd, pw, args);
         }
+        if (mUserSwitcherController != null) {
+            mUserSwitcherController.dump(fd, pw, args);
+        }
     }
 
     private String hunStateToString(Entry entry) {
@@ -3126,16 +3133,6 @@
             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);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 1bb36dd..8b5ff67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -27,11 +27,12 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
+import android.telecomm.TelecommConstants;
+import android.telecomm.TelecommManager;
 import android.util.Log;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.cdma.TtyIntent;
 import com.android.systemui.R;
 
 /**
@@ -89,7 +90,7 @@
             else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
                 updateSimState(intent);
             }
-            else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
+            else if (action.equals(TelecommConstants.ACTION_CURRENT_TTY_MODE_CHANGED)) {
                 updateTTY(intent);
             }
             else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
@@ -110,7 +111,7 @@
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-        filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
+        filter.addAction(TelecommConstants.ACTION_CURRENT_TTY_MODE_CHANGED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
 
@@ -269,7 +270,9 @@
     }
 
     private final void updateTTY(Intent intent) {
-        final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false);
+        int currentTtyMode = intent.getIntExtra(TelecommConstants.EXTRA_CURRENT_TTY_MODE,
+                TelecommConstants.TTY_MODE_OFF);
+        boolean enabled = currentTtyMode != TelecommConstants.TTY_MODE_OFF;
 
         if (DEBUG) Log.v(TAG, "updateTTY: enabled: " + enabled);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 5fbade1..a599070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.TetheringController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.volume.VolumeComponent;
 
@@ -63,12 +64,14 @@
     private final VolumeComponent mVolume;
     private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>();
     private final FlashlightController mFlashlight;
+    private final UserSwitcherController mUserSwitcherController;
 
     public QSTileHost(Context context, PhoneStatusBar statusBar,
             BluetoothController bluetooth, LocationController location,
             RotationLockController rotation, NetworkController network,
             ZenModeController zen, TetheringController tethering,
-            CastController cast, VolumeComponent volume, FlashlightController flashlight) {
+            CastController cast, VolumeComponent volume, FlashlightController flashlight,
+            UserSwitcherController userSwitcher) {
         mContext = context;
         mStatusBar = statusBar;
         mBluetooth = bluetooth;
@@ -80,6 +83,7 @@
         mCast = cast;
         mVolume = volume;
         mFlashlight = flashlight;
+        mUserSwitcherController = userSwitcher;
 
         final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName());
         ht.start();
@@ -181,4 +185,8 @@
     public FlashlightController getFlashlightController() {
         return mFlashlight;
     }
+
+    public UserSwitcherController getUserSwitcherController() {
+        return mUserSwitcherController;
+    }
 }
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 33fc479..dc06ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -586,7 +586,7 @@
                     mQsDetailHeader.setOnClickListener(new OnClickListener() {
                         @Override
                         public void onClick(View v) {
-                            detail.setToggleState(!toggleState);
+                            detail.setToggleState(!mQsDetailHeaderSwitch.isChecked());
                         }
                     });
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
new file mode 100644
index 0000000..4640067
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.qs.QSTile;
+import com.android.systemui.qs.tiles.UserDetailView;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.View;
+import android.view.ViewGroup;
+import android.view.WindowManagerGlobal;
+import android.widget.BaseAdapter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Keeps a list of all users on the device for user switching.
+ */
+public class UserSwitcherController {
+
+    private static final String TAG = "UserSwitcherController";
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
+
+    private ArrayList<UserRecord> mUsers = new ArrayList<>();
+
+    public UserSwitcherController(Context context) {
+        mContext = context;
+        mUserManager = UserManager.get(context);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        mContext.registerReceiver(mReceiver, filter);
+        refreshUsers();
+    }
+
+    private void refreshUsers() {
+        new AsyncTask<Void, Void, ArrayList<UserRecord>>() {
+
+            @Override
+            protected ArrayList<UserRecord> doInBackground(Void... params) {
+                List<UserInfo> infos = mUserManager.getUsers(true);
+                if (infos == null) {
+                    return null;
+                }
+                ArrayList<UserRecord> records = new ArrayList<>(infos.size());
+                int currentId = ActivityManager.getCurrentUser();
+                UserRecord guestRecord = null;
+
+                for (UserInfo info : infos) {
+                    boolean isCurrent = currentId == info.id;
+                    if (info.isGuest()) {
+                        guestRecord = new UserRecord(info, null /* picture */,
+                                true /* isGuest */, isCurrent);
+                    } else if (!info.isManagedProfile()) {
+                        records.add(new UserRecord(info, mUserManager.getUserIcon(info.id),
+                                false /* isGuest */, isCurrent));
+                    }
+                }
+
+                if (guestRecord == null) {
+                    records.add(new UserRecord(null /* info */, null /* picture */,
+                            true /* isGuest */, false /* isCurrent */));
+                } else {
+                    records.add(guestRecord);
+                }
+
+                return records;
+            }
+
+            @Override
+            protected void onPostExecute(ArrayList<UserRecord> userRecords) {
+                if (userRecords != null) {
+                    mUsers = userRecords;
+                    notifyAdapters();
+                }
+            }
+        }.execute((Void[])null);
+    }
+
+    private void notifyAdapters() {
+        for (int i = mAdapters.size() - 1; i >= 0; i--) {
+            BaseUserAdapter adapter = mAdapters.get(i).get();
+            if (adapter != null) {
+                adapter.notifyDataSetChanged();
+            } else {
+                mAdapters.remove(i);
+            }
+        }
+    }
+
+    public void switchTo(UserRecord record) {
+        int id;
+        if (record.isGuest && record.info == null) {
+            // No guest user. Create one.
+            id = mUserManager.createGuest(mContext,
+                    mContext.getResources().getString(R.string.guest_nickname)).id;
+        } else {
+            id = record.info.id;
+        }
+
+        if (ActivityManager.getCurrentUser() == id) {
+            return;
+        }
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().lockNow(null);
+            ActivityManagerNative.getDefault().switchUser(id);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't switch user.", e);
+        }
+    }
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                final int N = mUsers.size();
+                for (int i = 0; i < N; i++) {
+                    UserRecord record = mUsers.get(i);
+                    boolean shouldBeCurrent = record.info.id == currentId;
+                    if (record.isCurrent != shouldBeCurrent) {
+                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
+                    }
+                }
+                notifyAdapters();
+            } else {
+                refreshUsers();
+            }
+        }
+    };
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("UserSwitcherController state:");
+        pw.print("  mUsers.size="); pw.println(mUsers.size());
+        for (int i = 0; i < mUsers.size(); i++) {
+            final UserRecord u = mUsers.get(i);
+            pw.print("    "); pw.println(u.toString());
+        }
+    }
+
+    public static abstract class BaseUserAdapter extends BaseAdapter {
+
+        final UserSwitcherController mController;
+
+        protected BaseUserAdapter(UserSwitcherController controller) {
+            mController = controller;
+            controller.mAdapters.add(new WeakReference<>(this));
+        }
+
+        @Override
+        public int getCount() {
+            return mController.mUsers.size();
+        }
+
+        @Override
+        public UserRecord getItem(int position) {
+            return mController.mUsers.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return mController.mUsers.get(position).info.id;
+        }
+
+        public void switchTo(UserRecord record) {
+            mController.switchTo(record);
+        }
+    }
+
+    public static final class UserRecord {
+        public final UserInfo info;
+        public final Bitmap picture;
+        public final boolean isGuest;
+        public final boolean isCurrent;
+
+        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent) {
+            this.info = info;
+            this.picture = picture;
+            this.isGuest = isGuest;
+            this.isCurrent = isCurrent;
+        }
+
+        public UserRecord copyWithIsCurrent(boolean _isCurrent) {
+            return new UserRecord(info, picture, isGuest, _isCurrent);
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("UserRecord(");
+            if (info != null) {
+                sb.append("name=\"" + info.name + "\" id=" + info.id);
+            } else {
+                sb.append("<add guest placeholder>");
+            }
+            if (isGuest) {
+                sb.append(" <isGuest>");
+            }
+            if (isCurrent) {
+                sb.append(" <isCurrent>");
+            }
+            if (picture != null) {
+                sb.append(" <hasPicture>");
+            }
+            sb.append(')');
+            return sb.toString();
+        }
+    }
+
+    public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
+        private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
+
+        @Override
+        public int getTitle() {
+            return R.string.quick_settings_user_title;
+        }
+
+        @Override
+        public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            if (!(convertView instanceof UserDetailView)) {
+                convertView = UserDetailView.inflate(context, parent, false);
+            }
+            UserDetailView v = (UserDetailView) convertView;
+            if (v.getAdapter() == null) {
+                v.createAndSetAdapter(UserSwitcherController.this);
+            }
+            return v;
+        }
+
+        @Override
+        public Intent getSettingsIntent() {
+            return USER_SETTINGS_INTENT;
+        }
+
+        @Override
+        public Boolean getToggleState() {
+            return null;
+        }
+
+        @Override
+        public void setToggleState(boolean state) {
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 375f94c..e38c2ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -189,7 +189,7 @@
         @Override
         public void remoteVolumeChanged(ISessionController binder, int flags)
                 throws RemoteException {
-            MediaController controller = MediaController.fromBinder(binder);
+            MediaController controller = new MediaController(binder);
             mPanel.postRemoteVolumeChanged(controller, flags);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 9c166ac..cf04219 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -32,7 +32,6 @@
 import android.service.notification.ZenModeConfig;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AnimationUtils;
@@ -54,8 +53,11 @@
     private static final String TAG = "ZenModePanel";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private static final int SECONDS_MS = 1000;
+    private static final int MINUTES_MS = 60 * SECONDS_MS;
+
     private static final int[] MINUTE_BUCKETS = DEBUG
-            ? new int[] { 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 }
+            ? new int[] { 0, 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 }
             : new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
     private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
     private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
@@ -64,9 +66,7 @@
     private static final int TIME_CONDITION_INDEX = 1;
     private static final int FIRST_CONDITION_INDEX = 2;
     private static final float SILENT_HINT_PULSE_SCALE = 1.1f;
-
-    private static final int SECONDS_MS = 1000;
-    private static final int MINUTES_MS = 60 * SECONDS_MS;
+    private static final int ZERO_VALUE_MS = 20 * SECONDS_MS;
 
     public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
 
@@ -152,6 +152,7 @@
         if (DEBUG) Log.d(mTag, "onAttachedToWindow");
         mAttachedZen = getSelectedZen(-1);
         refreshExitConditionText();
+        updateWidgets();
     }
 
     @Override
@@ -292,7 +293,8 @@
 
     private Condition newTimeCondition(int minutesFromNow) {
         final long now = System.currentTimeMillis();
-        return timeCondition(now + minutesFromNow * MINUTES_MS, minutesFromNow);
+        final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
+        return timeCondition(now + millis, minutesFromNow);
     }
 
     private Condition timeCondition(long time, int minutes) {
@@ -369,6 +371,9 @@
         }
         tag.conditionId = condition != null ? condition.id : null;
         tag.rb.setEnabled(enabled);
+        if (Objects.equals(tag.conditionId, mExitConditionId)) {
+            tag.rb.setChecked(true);
+        }
         tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
             @Override
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f431fdb..edeb5b4 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1700,7 +1700,7 @@
                 // If we have a session send it the volume command, otherwise
                 // use the suggested stream.
                 if (mMediaController != null) {
-                    mMediaController.adjustVolumeBy(direction, AudioManager.FLAG_SHOW_UI);
+                    mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
                 } else {
                     MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
                             mVolumeControlStreamType, direction, AudioManager.FLAG_SHOW_UI);
@@ -1787,7 +1787,7 @@
                 // If we have a session send it the volume command, otherwise
                 // use the suggested stream.
                 if (mMediaController != null) {
-                    mMediaController.adjustVolumeBy(0, AudioManager.FLAG_PLAY_SOUND
+                    mMediaController.adjustVolume(0, AudioManager.FLAG_PLAY_SOUND
                             | AudioManager.FLAG_VIBRATE);
                 } else {
                     MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index ef15a80..608aa44 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -250,6 +250,9 @@
     // Vibrator pattern for a short vibration when tapping on an hour/minute tick of a Clock.
     long[] mClockTickVibePattern;
 
+    // Vibrator pattern for a short vibration when tapping on a day/month/year date of a Calendar.
+    long[] mCalendarDateVibePattern;
+
     // Vibrator pattern for haptic feedback during boot when safe mode is disabled.
     long[] mSafeModeDisabledVibePattern;
 
@@ -1071,6 +1074,8 @@
                 com.android.internal.R.array.config_keyboardTapVibePattern);
         mClockTickVibePattern = getLongIntArray(mContext.getResources(),
                 com.android.internal.R.array.config_clockTickVibePattern);
+        mCalendarDateVibePattern = getLongIntArray(mContext.getResources(),
+                com.android.internal.R.array.config_calendarDateVibePattern);
         mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(),
                 com.android.internal.R.array.config_safeModeDisabledVibePattern);
         mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
@@ -5406,6 +5411,9 @@
             case HapticFeedbackConstants.CLOCK_TICK:
                 pattern = mClockTickVibePattern;
                 break;
+            case HapticFeedbackConstants.CALENDAR_DATE:
+                pattern = mCalendarDateVibePattern;
+                break;
             case HapticFeedbackConstants.SAFE_MODE_DISABLED:
                 pattern = mSafeModeDisabledVibePattern;
                 break;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index c3a9dbe..e8e2813 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3535,19 +3535,30 @@
                                 }
                             } 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);
+                            // In all cases we need to give the transport its finish callback
+                            int finishResult = transport.finishBackup();
+
+                            // If we were otherwise in a good state, now interpret the final
+                            // result based on what finishBackup() returned.  If we're in a
+                            // failure case already, preserve that result and ignore whatever
+                            // finishBackup() reported.
+                            if (result == BackupTransport.TRANSPORT_OK) {
+                                result = finishResult;
                             }
-                        } else if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+
+                            if (result != BackupTransport.TRANSPORT_OK) {
+                                Slog.e(TAG, "Error " + result
+                                        + " backing up " + target.packageName);
+                            }
+                        }
+
+                        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 {
+                        } else if (result != BackupTransport.TRANSPORT_OK) {
                             if (DEBUG) {
                                 Slog.i(TAG, "Transport failed; aborting backup");
                                 return;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6554ed3..86f8777 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3199,13 +3199,17 @@
                     break;
                 }
                 case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
-                        break;
+                    if (msg.arg1 == 0) {
+                        setProvNotificationVisibleIntent(false, msg.arg2, 0, null, null);
+                    } else {
+                        NetworkAgentInfo nai = mNetworkForNetId.get(msg.arg2);
+                        if (nai == null) {
+                            loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
+                            break;
+                        }
+                        setProvNotificationVisibleIntent(true, msg.arg2, nai.networkInfo.getType(),
+                                nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj);
                     }
-                    setProvNotificationVisibleIntent(msg.arg1 != 0, nai.networkInfo.getType(),
-                            nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj);
                     break;
                 }
                 case NetworkStateTracker.EVENT_STATE_CHANGED: {
@@ -4958,10 +4962,19 @@
                     break;
             }
         }
-        setProvNotificationVisibleIntent(visible, networkType, extraInfo, pendingIntent);
+        // Concatenate the range of types onto the range of NetIDs.
+        int id = MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE);
+        setProvNotificationVisibleIntent(visible, id, networkType, extraInfo, pendingIntent);
     }
 
-    private void setProvNotificationVisibleIntent(boolean visible, int networkType,
+    /**
+     * Show or hide network provisioning notificaitons.
+     *
+     * @param id an identifier that uniquely identifies this notification.  This must match
+     *         between show and hide calls.  We use the NetID value but for legacy callers
+     *         we concatenate the range of types with the range of NetIDs.
+     */
+    private void setProvNotificationVisibleIntent(boolean visible, int id, int networkType,
             String extraInfo, PendingIntent intent) {
         if (DBG) {
             log("setProvNotificationVisibleIntent: E visible=" + visible + " networkType=" +
@@ -5008,14 +5021,14 @@
             notification.contentIntent = intent;
 
             try {
-                notificationManager.notify(NOTIFICATION_ID, networkType, notification);
+                notificationManager.notify(NOTIFICATION_ID, id, notification);
             } catch (NullPointerException npe) {
                 loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
                 npe.printStackTrace();
             }
         } else {
             try {
-                notificationManager.cancel(NOTIFICATION_ID, networkType);
+                notificationManager.cancel(NOTIFICATION_ID, id);
             } catch (NullPointerException npe) {
                 loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
                 npe.printStackTrace();
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index a19eb15..87084d5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1069,27 +1069,31 @@
         synchronized (mRecords) {
             final int recordCount = mRecords.size();
             pw.println("last known state:");
-            pw.println("  mCallState=" + mCallState);
-            pw.println("  mCallIncomingNumber=" + mCallIncomingNumber);
-            pw.println("  mServiceState=" + mServiceState);
-            pw.println("  mSignalStrength=" + mSignalStrength);
-            pw.println("  mMessageWaiting=" + mMessageWaiting);
-            pw.println("  mCallForwarding=" + mCallForwarding);
-            pw.println("  mDataActivity=" + mDataActivity);
-            pw.println("  mDataConnectionState=" + mDataConnectionState);
-            pw.println("  mDataConnectionPossible=" + mDataConnectionPossible);
-            pw.println("  mDataConnectionReason=" + mDataConnectionReason);
-            pw.println("  mDataConnectionApn=" + mDataConnectionApn);
-            pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
-            pw.println("  mDataConnectionNetworkCapabilities=" +
-                    mDataConnectionNetworkCapabilities);
+            for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+                pw.println("  Phone Id=" + i);
+                pw.println("  mCallState=" + mCallState[i]);
+                pw.println("  mCallIncomingNumber=" + mCallIncomingNumber[i]);
+                pw.println("  mServiceState=" + mServiceState[i]);
+                pw.println("  mSignalStrength=" + mSignalStrength[i]);
+                pw.println("  mMessageWaiting=" + mMessageWaiting[i]);
+                pw.println("  mCallForwarding=" + mCallForwarding[i]);
+                pw.println("  mDataActivity=" + mDataActivity[i]);
+                pw.println("  mDataConnectionState=" + mDataConnectionState[i]);
+                pw.println("  mDataConnectionPossible=" + mDataConnectionPossible[i]);
+                pw.println("  mDataConnectionReason=" + mDataConnectionReason[i]);
+                pw.println("  mDataConnectionApn=" + mDataConnectionApn[i]);
+                pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
+                pw.println("  mDataConnectionNetworkCapabilities=" +
+                        mDataConnectionNetworkCapabilities[i]);
+                pw.println("  mCellLocation=" + mCellLocation[i]);
+                pw.println("  mCellInfo=" + mCellInfo.get(i));
+            }
             pw.println("  mDefaultSubId=" + mDefaultSubId);
-            pw.println("  mCellLocation=" + mCellLocation);
-            pw.println("  mCellInfo=" + mCellInfo);
             pw.println("  mDcRtInfo=" + mDcRtInfo);
             pw.println("registrations: count=" + recordCount);
             for (Record r : mRecords) {
                 pw.println("  " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events));
+                pw.println("is Legacy = " + r.isLegacyApp + " subId = " + r.subId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 543384f..1316da1 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1811,10 +1811,18 @@
                 break;
             }
             case SYSTEM_USER_START_MSG: {
+                mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+                        Integer.toString(msg.arg1), msg.arg1);
                 mSystemServiceManager.startUser(msg.arg1);
                 break;
             }
             case SYSTEM_USER_CURRENT_MSG: {
+                mBatteryStatsService.noteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
+                        Integer.toString(msg.arg2), msg.arg2);
+                mBatteryStatsService.noteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+                        Integer.toString(msg.arg1), msg.arg1);
                 mSystemServiceManager.switchUser(msg.arg1);
                 break;
             }
@@ -2801,7 +2809,7 @@
     void ensurePackageDexOpt(String packageName) {
         IPackageManager pm = AppGlobals.getPackageManager();
         try {
-            if (pm.performDexOpt(packageName)) {
+            if (pm.performDexOptIfNeeded(packageName, null /* instruction set */)) {
                 mDidDexOpt = true;
             }
         } catch (RemoteException e) {
@@ -5655,6 +5663,11 @@
     }
 
     @Override
+    public final void notifyLaunchTaskBehindComplete(IBinder token) {
+        mStackSupervisor.scheduleLaunchTaskBehindComplete(token);
+    }
+
+    @Override
     public String getCallingPackage(IBinder token) {
         synchronized (this) {
             ActivityRecord r = getCallingRecordLocked(token);
@@ -10163,7 +10176,11 @@
         }
 
         if (goingCallback != null) goingCallback.run();
-        
+
+        mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+                Integer.toString(mCurrentUserId), mCurrentUserId);
+        mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+                Integer.toString(mCurrentUserId), mCurrentUserId);
         mSystemServiceManager.startUser(mCurrentUserId);
 
         synchronized (this) {
@@ -12436,12 +12453,11 @@
                 pw.print(" lastCachedPss="); pw.println(r.lastCachedPss);
                 pw.print(prefix);
                 pw.print("    ");
-                pw.print("keeping="); pw.print(r.keeping);
-                pw.print(" cached="); pw.print(r.cached);
+                pw.print("cached="); pw.print(r.cached);
                 pw.print(" empty="); pw.print(r.empty);
                 pw.print(" hasAboveClient="); pw.println(r.hasAboveClient);
 
-                if (!r.keeping) {
+                if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
                     if (r.lastWakeTime != 0) {
                         long wtime;
                         BatteryStatsImpl stats = service.mBatteryStatsService.getActiveStatistics();
@@ -15195,7 +15211,6 @@
             app.adjSeq = mAdjSeq;
             app.curRawAdj = app.maxAdj;
             app.foregroundActivities = false;
-            app.keeping = true;
             app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
             app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT;
             // System processes can do UI, and when they do we want to have
@@ -15219,7 +15234,6 @@
             return (app.curAdj=app.maxAdj);
         }
 
-        app.keeping = false;
         app.systemNoUi = false;
 
         // Determine the importance of the process, starting with most
@@ -15464,9 +15478,6 @@
                         app.adjType = "cch-started-services";
                     }
                 }
-                // Don't kill this process because it is doing work; it
-                // has said it is doing work.
-                app.keeping = true;
             }
             for (int conni = s.connections.size()-1;
                     conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
@@ -15556,9 +15567,6 @@
                                 if (!client.cached) {
                                     app.cached = false;
                                 }
-                                if (client.keeping) {
-                                    app.keeping = true;
-                                }
                                 adjType = "service";
                             }
                         }
@@ -15670,7 +15678,6 @@
                         app.adjType = "provider";
                     }
                     app.cached &= client.cached;
-                    app.keeping |= client.keeping;
                     app.adjTypeCode = ActivityManager.RunningAppProcessInfo
                             .REASON_PROVIDER_IN_USE;
                     app.adjSource = client;
@@ -15714,7 +15721,6 @@
                     adj = ProcessList.FOREGROUND_APP_ADJ;
                     schedGroup = Process.THREAD_GROUP_DEFAULT;
                     app.cached = false;
-                    app.keeping = true;
                     app.adjType = "provider";
                     app.adjTarget = cpr.name;
                 }
@@ -15797,9 +15803,6 @@
                 schedGroup = Process.THREAD_GROUP_DEFAULT;
             }
         }
-        if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
-            app.keeping = true;
-        }
 
         // Do final modification to adj.  Everything we do between here and applying
         // the final setAdj must be done in this function, because we will also use
@@ -16021,7 +16024,7 @@
         while (i > 0) {
             i--;
             ProcessRecord app = mLruProcesses.get(i);
-            if (!app.keeping) {
+            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
                 long wtime;
                 synchronized (stats) {
                     wtime = stats.getProcessWakeTime(app.info.uid,
@@ -16066,7 +16069,7 @@
                             + " during " + realtimeSince);
                     app.baseProcessTracker.reportExcessiveWake(app.pkgList);
                 } else if (doCpuKills && uptimeSince > 0
-                        && ((cputimeUsed*100)/uptimeSince) >= 50) {
+                        && ((cputimeUsed*100)/uptimeSince) >= 25) {
                     synchronized (stats) {
                         stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
                                 uptimeSince, cputimeUsed);
@@ -16082,23 +16085,11 @@
         }
     }
 
-    private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping,
+    private final boolean applyOomAdjLocked(ProcessRecord app,
             ProcessRecord TOP_APP, boolean doingAll, long now) {
         boolean success = true;
 
         if (app.curRawAdj != app.setRawAdj) {
-            if (wasKeeping && !app.keeping) {
-                // This app is no longer something we want to keep.  Note
-                // its current wake lock time to later know to kill it if
-                // it is not behaving well.
-                BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-                synchronized (stats) {
-                    app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
-                            app.pid, SystemClock.elapsedRealtime());
-                }
-                app.lastCpuTime = app.curCpuTime;
-            }
-
             app.setRawAdj = app.curRawAdj;
         }
 
@@ -16187,6 +16178,21 @@
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
                     "Proc state change of " + app.processName
                     + " to " + app.curProcState);
+            boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
+            boolean curImportant = app.curProcState < ActivityManager.PROCESS_STATE_SERVICE;
+            if (setImportant && !curImportant) {
+                // This app is no longer something we consider important enough to allow to
+                // use arbitrary amounts of battery power.  Note
+                // its current wake lock time to later know to kill it if
+                // it is not behaving well.
+                BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+                synchronized (stats) {
+                    app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
+                            app.pid, SystemClock.elapsedRealtime());
+                }
+                app.lastCpuTime = app.curCpuTime;
+
+            }
             app.setProcState = app.curProcState;
             if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
                 app.notCachedSinceIdle = false;
@@ -16263,11 +16269,9 @@
             return false;
         }
 
-        final boolean wasKeeping = app.keeping;
-
         computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
 
-        return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll, now);
+        return applyOomAdjLocked(app, TOP_APP, doingAll, now);
     }
 
     final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
@@ -16423,7 +16427,6 @@
             ProcessRecord app = mLruProcesses.get(i);
             if (!app.killedByAm && app.thread != null) {
                 app.procStateChanged = false;
-                final boolean wasKeeping = app.keeping;
                 computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
 
                 // If we haven't yet assigned the final cached adj
@@ -16478,7 +16481,7 @@
                     }
                 }
 
-                applyOomAdjLocked(app, wasKeeping, TOP_APP, true, now);
+                applyOomAdjLocked(app, TOP_APP, true, now);
 
                 // Count the number of process types.
                 switch (app.curProcState) {
@@ -17122,7 +17125,8 @@
                 }
 
                 if (foreground) {
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId));
+                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+                            oldUserId));
                     mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
                     mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
                     mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
@@ -17497,6 +17501,9 @@
                             }
                             uss.mState = UserStartedState.STATE_SHUTDOWN;
                         }
+                        mBatteryStatsService.noteEvent(
+                                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
+                                Integer.toString(userId), userId);
                         mSystemServiceManager.stopUser(userId);
                         broadcastIntentLocked(null, null, shutdownIntent,
                                 null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 46521c5..6c47922 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -167,6 +167,8 @@
     ActivityContainer mInitialActivityContainer;
 
     TaskDescription taskDescription; // the recents information for this activity
+    boolean mLaunchTaskBehind; // this activity is actively being launched with
+        // ActivityOptions.setLaunchTaskBehind, will be cleared once launch is completed.
 
     void dump(PrintWriter pw, String prefix) {
         final long now = SystemClock.uptimeMillis();
@@ -400,6 +402,7 @@
         mInitialActivityContainer = container;
         if (options != null) {
             pendingOptions = new ActivityOptions(options);
+            mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind();
         }
 
         // This starts out true, since the initial state of an activity
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 260a5b5..32f2624 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1081,6 +1081,7 @@
         if (next == mLastScreenshotActivity) {
             invalidateLastScreenshot();
         }
+        mReturningActivityOptions = null;
     }
 
     private void setVisibile(ActivityRecord r, boolean visible) {
@@ -1134,19 +1135,15 @@
         return true;
     }
 
-    final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
-        ActivityRecord r = topRunningActivityLocked(null);
-        if (r != null) {
-            ensureActivitiesVisibleLocked(r, starting, null, configChanges);
-        }
-    }
-
     /**
      * Make sure that all activities that need to be visible (that is, they
      * currently can be seen by the user) actually are.
      */
-    final void ensureActivitiesVisibleLocked(ActivityRecord top, ActivityRecord starting,
-            String onlyThisProcess, int configChanges) {
+    final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
+        ActivityRecord top = topRunningActivityLocked(null);
+        if (top == null) {
+            return;
+        }
         if (DEBUG_VISBILITY) Slog.v(
                 TAG, "ensureActivitiesVisible behind " + top
                 + " configChanges=0x" + Integer.toHexString(configChanges));
@@ -1178,37 +1175,34 @@
                     continue;
                 }
                 aboveTop = false;
-                if (!behindFullscreen) {
+                // mLaunchingBehind: Activities launching behind are at the back of the task stack
+                // but must be drawn initially for the animation as though they were visible.
+                if (!behindFullscreen || r.mLaunchTaskBehind) {
                     if (DEBUG_VISBILITY) Slog.v(
                             TAG, "Make visible? " + r + " finishing=" + r.finishing
                             + " state=" + r.state);
 
-                    final boolean doThisProcess = onlyThisProcess == null
-                            || onlyThisProcess.equals(r.processName);
-
                     // First: if this is not the current activity being started, make
                     // sure it matches the current configuration.
-                    if (r != starting && doThisProcess) {
+                    if (r != starting) {
                         ensureActivityConfigurationLocked(r, 0);
                     }
 
                     if (r.app == null || r.app.thread == null) {
-                        if (onlyThisProcess == null || onlyThisProcess.equals(r.processName)) {
-                            // This activity needs to be visible, but isn't even
-                            // running...  get it started, but don't resume it
-                            // at this point.
-                            if (DEBUG_VISBILITY) Slog.v(TAG, "Start and freeze screen for " + r);
-                            if (r != starting) {
-                                r.startFreezingScreenLocked(r.app, configChanges);
-                            }
-                            if (!r.visible) {
-                                if (DEBUG_VISBILITY) Slog.v(
-                                        TAG, "Starting and making visible: " + r);
-                                setVisibile(r, true);
-                            }
-                            if (r != starting) {
-                                mStackSupervisor.startSpecificActivityLocked(r, false, false);
-                            }
+                        // This activity needs to be visible, but isn't even
+                        // running...  get it started, but don't resume it
+                        // at this point.
+                        if (DEBUG_VISBILITY) Slog.v(TAG, "Start and freeze screen for " + r);
+                        if (r != starting) {
+                            r.startFreezingScreenLocked(r.app, configChanges);
+                        }
+                        if (!r.visible || r.mLaunchTaskBehind) {
+                            if (DEBUG_VISBILITY) Slog.v(
+                                    TAG, "Starting and making visible: " + r);
+                            setVisibile(r, true);
+                        }
+                        if (r != starting) {
+                            mStackSupervisor.startSpecificActivityLocked(r, false, false);
                         }
 
                     } else if (r.visible) {
@@ -1217,17 +1211,14 @@
                         if (DEBUG_VISBILITY) Slog.v(TAG, "Skipping: already visible at " + r);
                         r.stopFreezingScreenLocked(false);
                         try {
-                            if (mReturningActivityOptions != null) {
-                                if (activityNdx > 0) {
-                                    ActivityRecord under = activities.get(activityNdx - 1);
-                                    under.app.thread.scheduleOnNewActivityOptions(under.appToken,
-                                            mReturningActivityOptions);
-                                }
-                                mReturningActivityOptions = null;
+                            if (mReturningActivityOptions != null && r == top && activityNdx > 0) {
+                                ActivityRecord under = activities.get(activityNdx - 1);
+                                under.app.thread.scheduleOnNewActivityOptions(under.appToken,
+                                        mReturningActivityOptions);
                             }
                         } catch(RemoteException e) {
                         }
-                    } else if (onlyThisProcess == null) {
+                    } else {
                         // This activity is not currently visible, but is running.
                         // Tell it to become visible.
                         r.visible = true;
@@ -1650,7 +1641,9 @@
                 } else {
                     mWindowManager.prepareAppTransition(prev.task == next.task
                             ? AppTransition.TRANSIT_ACTIVITY_OPEN
-                            : AppTransition.TRANSIT_TASK_OPEN, false);
+                            : next.mLaunchTaskBehind
+                                    ? AppTransition.TRANSIT_TASK_OPEN_BEHIND
+                                    : AppTransition.TRANSIT_TASK_OPEN, false);
                 }
             }
             if (false) {
@@ -1842,8 +1835,11 @@
             ActivityStack lastStack = mStackSupervisor.getLastStack();
             final boolean fromHome = lastStack.isHomeStack();
             if (!isHomeStack() && (fromHome || topTask() != task)) {
-                task.setTaskToReturnTo(fromHome ?
-                        lastStack.topTask().taskType : APPLICATION_ACTIVITY_TYPE);
+                task.setTaskToReturnTo(fromHome
+                        ? lastStack.topTask() == null
+                                ? HOME_ACTIVITY_TYPE
+                                : lastStack.topTask().taskType
+                        : APPLICATION_ACTIVITY_TYPE);
             }
         } else {
             task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
@@ -1851,17 +1847,17 @@
 
         mTaskHistory.remove(task);
         // Now put task at top.
-        int stackNdx = mTaskHistory.size();
+        int taskNdx = mTaskHistory.size();
         if (!isCurrentProfileLocked(task.userId)) {
             // Put non-current user tasks below current user tasks.
-            while (--stackNdx >= 0) {
-                if (!isCurrentProfileLocked(mTaskHistory.get(stackNdx).userId)) {
+            while (--taskNdx >= 0) {
+                if (!isCurrentProfileLocked(mTaskHistory.get(taskNdx).userId)) {
                     break;
                 }
             }
-            ++stackNdx;
+            ++taskNdx;
         }
-        mTaskHistory.add(stackNdx, task);
+        mTaskHistory.add(taskNdx, task);
         updateTaskMovement(task, true);
     }
 
@@ -1869,7 +1865,8 @@
             boolean doResume, boolean keepCurTransition, Bundle options) {
         TaskRecord rTask = r.task;
         final int taskId = rTask.taskId;
-        if (taskForIdLocked(taskId) == null || newTask) {
+        // mLaunchTaskBehind tasks get placed at the back of the task stack.
+        if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
             // Last activity in task had been removed or ActivityManagerService is reusing task.
             // Insert or replace.
             // Might not even be in.
@@ -1894,7 +1891,8 @@
                         mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                                 r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                                 (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
-                                r.userId, r.info.configChanges, task.voiceSession != null);
+                                r.userId, r.info.configChanges, task.voiceSession != null,
+                                r.mLaunchTaskBehind);
                         if (VALIDATE_TOKENS) {
                             validateAppTokensLocked();
                         }
@@ -1948,14 +1946,16 @@
                 mNoAnimActivities.add(r);
             } else {
                 mWindowManager.prepareAppTransition(newTask
-                        ? AppTransition.TRANSIT_TASK_OPEN
+                        ? r.mLaunchTaskBehind
+                                ? AppTransition.TRANSIT_TASK_OPEN_BEHIND
+                                : AppTransition.TRANSIT_TASK_OPEN
                         : AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
                 mNoAnimActivities.remove(r);
             }
             mWindowManager.addAppToken(task.mActivities.indexOf(r),
                     r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                     (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
-                    r.info.configChanges, task.voiceSession != null);
+                    r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
             boolean doShow = true;
             if (newTask) {
                 // Even though this activity is starting fresh, we still need
@@ -1971,7 +1971,12 @@
                     == ActivityOptions.ANIM_SCENE_TRANSITION) {
                 doShow = false;
             }
-            if (SHOW_APP_STARTING_PREVIEW && doShow) {
+            if (r.mLaunchTaskBehind) {
+                // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
+                // tell WindowManager that r is visible even though it is at the back of the stack.
+                mWindowManager.setAppVisibility(r.appToken, true);
+                ensureActivitiesVisibleLocked(null, 0);
+            } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
                 // Figure out if we are transitioning from another activity that is
                 // "has the same starting icon" as the next one.  This allows the
                 // window manager to keep the previous window it had previously
@@ -2002,7 +2007,7 @@
             mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                     r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                     (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
-                    r.info.configChanges, task.voiceSession != null);
+                    r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
             ActivityOptions.abort(options);
             options = null;
         }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index bc184c6..b0dfe4a 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -144,6 +144,7 @@
     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;
+    static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 13;
 
     private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
 
@@ -1557,9 +1558,9 @@
                     break;
             }
         }
-        final int launchBehindFlags = Intent.FLAG_ACTIVITY_LAUNCH_BEHIND |
-                Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-        final boolean affiliateTask = (launchFlags & launchBehindFlags) == launchBehindFlags;
+
+        final boolean launchTaskBehind = r.mLaunchTaskBehind &&
+                (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
 
         if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
             // For whatever reason this activity is being launched into a new
@@ -1709,7 +1710,7 @@
                                 sourceStack.topActivity().task == sourceRecord.task)) {
                             // We really do want to push this one into the
                             // user's face, right now.
-                            if (affiliateTask && sourceRecord != null) {
+                            if (launchTaskBehind && sourceRecord != null) {
                                 intentActivity.setTaskToAffiliateWith(sourceRecord.task);
                             }
                             movedHome = true;
@@ -1886,7 +1887,7 @@
         boolean newTask = false;
         boolean keepCurTransition = false;
 
-        TaskRecord taskToAffiliate = affiliateTask && sourceRecord != null ?
+        TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?
                 sourceRecord.task : null;
 
         // Should this be considered a new task?
@@ -1898,12 +1899,15 @@
             }
             newTask = true;
             targetStack = adjustStackFocus(r, newTask);
-            targetStack.moveToFront();
+            if (!launchTaskBehind) {
+                targetStack.moveToFront();
+            }
             if (reuseTask == null) {
                 r.setTask(targetStack.createTaskRecord(getNextTaskId(),
                         newTaskInfo != null ? newTaskInfo : r.info,
                         newTaskIntent != null ? newTaskIntent : intent,
-                        voiceSession, voiceInteractor, true), taskToAffiliate);
+                        voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),
+                        taskToAffiliate);
                 if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
                         r.task);
             } else {
@@ -1997,7 +2001,10 @@
         ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
         targetStack.mLastPausedActivity = null;
         targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);
-        mService.setFocusedActivityLocked(r);
+        if (!launchTaskBehind) {
+            // Don't set focus on an activity that's going to the back.
+            mService.setFocusedActivityLocked(r);
+        }
         return ActivityManager.START_SUCCESS;
     }
 
@@ -2394,7 +2401,8 @@
                 mWindowManager.addAppToken(0, r.appToken, taskId, stackId,
                         r.info.screenOrientation, r.fullscreen,
                         (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
-                        r.userId, r.info.configChanges, task.voiceSession != null);
+                        r.userId, r.info.configChanges, task.voiceSession != null,
+                        r.mLaunchTaskBehind);
             }
             mWindowManager.addTask(taskId, stackId, false);
         }
@@ -2642,6 +2650,19 @@
         return true;
     }
 
+    // Called when WindowManager has finished animating the launchingBehind activity to the back.
+    void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
+        r.mLaunchTaskBehind = false;
+        final TaskRecord task = r.task;
+        task.setLastThumbnail(task.stack.screenshotActivities(r));
+        mService.addRecentTaskLocked(task);
+        mWindowManager.setAppVisibility(r.appToken, false);
+    }
+
+    void scheduleLaunchTaskBehindComplete(IBinder token) {
+        mHandler.obtainMessage(LAUNCH_TASK_BEHIND_COMPLETE, token).sendToTarget();
+    }
+
     void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
         // First the front stacks. In case any are not fullscreen and are in front of home.
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
@@ -3268,6 +3289,7 @@
                                     Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0;
                             if (shouldLockKeyguard) {
                                 mWindowManager.lockNow(null);
+                                mWindowManager.dismissKeyguard();
                             }
                         } catch (SettingNotFoundException e) {
                             // No setting, don't lock.
@@ -3293,6 +3315,14 @@
                         ((ActivityContainer) msg.obj).onTaskListEmptyLocked();
                     }
                 } break;
+                case LAUNCH_TASK_BEHIND_COMPLETE: {
+                    synchronized (mService) {
+                        ActivityRecord r = ActivityRecord.forToken((IBinder) msg.obj);
+                        if (r != null) {
+                            handleLaunchTaskBehindCompleteLocked(r);
+                        }
+                    }
+                } break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index da444f9..ac19bde 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -186,6 +186,34 @@
         }
     }
 
+    public void noteSyncStart(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteSyncStartLocked(name, uid);
+        }
+    }
+
+    public void noteSyncFinish(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteSyncFinishLocked(name, uid);
+        }
+    }
+
+    public void noteJobStart(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteJobStartLocked(name, uid);
+        }
+    }
+
+    public void noteJobFinish(String name, int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteJobFinishLocked(name, uid);
+        }
+    }
+
     public void noteStartWakelock(int uid, int pid, String name, String historyName, int type,
             boolean unimportantForLogging) {
         enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index fe39744..6f9b23d 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -16,28 +16,9 @@
 
 package com.android.server.am;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.BitmapFactory;
-import android.graphics.PixelFormat;
 import android.os.Handler;
 import android.os.Message;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.DynamicDrawableSpan;
-import android.text.style.ImageSpan;
-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 android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -49,20 +30,12 @@
 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 showToast(boolean isLocked) {
@@ -70,163 +43,25 @@
     }
 
     public void handleShowToast(boolean isLocked) {
-        final Resources r = Resources.getSystem();
         String text = mContext.getString(isLocked
                 ? R.string.lock_to_app_toast_locked : R.string.lock_to_app_toast);
-        Toast toast = Toast.makeText(mContext, text, Toast.LENGTH_LONG);
-        TextView tv = (TextView) toast.getView().findViewById(R.id.message);
-
-        if (isLocked) {
-            tv.setText(text);
-        } else {
-            final SpannableString formattedText =
-                    new SpannableString(text.replace('$', ' '));
-            final ImageSpan imageSpan = new ImageSpan(mContext,
-                    BitmapFactory.decodeResource(r, R.drawable.ic_recent),
-                    DynamicDrawableSpan.ALIGN_BOTTOM);
-            final int index = text.indexOf('$');
-            if (index >= 0) {
-                formattedText.setSpan(imageSpan, index, index + 1,
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-
-            // Make icon fit.
-            final float width = imageSpan.getDrawable().getIntrinsicWidth();
-            final float height = imageSpan.getDrawable().getIntrinsicHeight();
-            final int lineHeight = tv.getLineHeight();
-            imageSpan.getDrawable().setBounds(0, 0, (int) (lineHeight * width / height),
-                    lineHeight);
-
-            tv.setText(formattedText);
-        }
-
-
-        toast.show();
+        Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
     }
 
     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;
+        int showString = R.string.lock_to_app_exit;
+        if (starting) {
+            showString = R.string.lock_to_app_start;
         }
-    }
-
-
-    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;
-        }
+        Toast.makeText(mContext, mContext.getString(showString), Toast.LENGTH_LONG).show();
     }
 
     private final class H extends Handler {
-        private static final int SHOW = 1;
-        private static final int HIDE = 2;
         private static final int SHOW_TOAST = 3;
 
         @Override
         public void handleMessage(Message msg) {
             switch(msg.what) {
-                case SHOW:
-                    handleShow();
-                    break;
-                case HIDE:
-                    handleHide();
-                    break;
                 case SHOW_TOAST:
                     handleShowToast(msg.arg1 != 0);
                     break;
diff --git a/services/core/java/com/android/server/am/LockToAppRequestDialog.java b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
index 6e86dff..0847b52 100644
--- a/services/core/java/com/android/server/am/LockToAppRequestDialog.java
+++ b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
@@ -2,21 +2,23 @@
 package com.android.server.am;
 
 import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.res.Resources;
-import android.graphics.BitmapFactory;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.DynamicDrawableSpan;
-import android.text.style.ImageSpan;
+import android.provider.Settings.SettingNotFoundException;
 import android.util.Slog;
 import android.view.WindowManager;
-import android.widget.TextView;
+import android.widget.CheckBox;
 
 import com.android.internal.R;
+import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtilsCache;
 
 public class LockToAppRequestDialog implements OnClickListener {
     private static final String TAG = "ActivityManager";
@@ -27,55 +29,95 @@
     private AlertDialog mDialog;
     private TaskRecord mRequestedTask;
 
+    private CheckBox mCheckbox;
+
+    private ILockSettings mLockSettingsService;
+
     public LockToAppRequestDialog(Context context, ActivityManagerService activityManagerService) {
         mContext = context;
         mService = activityManagerService;
     }
 
+    private ILockSettings getLockSettings() {
+        if (mLockSettingsService == null) {
+            mLockSettingsService = LockPatternUtilsCache.getInstance(
+                    ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")));
+        }
+        return mLockSettingsService;
+    }
+
+    private int getLockString(int userId) {
+        try {
+            int quality = (int) getLockSettings().getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
+            switch (quality) {
+                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+                    return R.string.lock_to_app_unlock_pin;
+                case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+                case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+                case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+                    return R.string.lock_to_app_unlock_password;
+                case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+                    if (getLockSettings().getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false,
+                            userId)) {
+                        return R.string.lock_to_app_unlock_pattern;
+                    }
+            }
+        } catch (RemoteException e) {
+        }
+        return 0;
+    }
+
     public void showLockTaskPrompt(TaskRecord task) {
         if (mDialog != null) {
             mDialog.dismiss();
             mDialog = null;
         }
         mRequestedTask = task;
+        final int unlockStringId = getLockString(task.userId);
 
         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)
+        final String description= r.getString(R.string.lock_to_app_description);
+        AlertDialog.Builder builder = 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();
+                        .setNegativeButton(r.getString(R.string.lock_to_app_negative), this);
+        if (unlockStringId != 0) {
+            builder.setView(R.layout.lock_to_app_checkbox);
+        }
+        mDialog = builder.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);
+        if (unlockStringId != 0) {
+            String unlockString = mContext.getString(unlockStringId);
+            mCheckbox = (CheckBox) mDialog.findViewById(R.id.lock_to_app_checkbox);
+            mCheckbox.setText(mContext.getString(R.string.lock_to_app_use_screen_lock,
+                    unlockString));
+
+            // Remember state.
+            try {
+                boolean useLock = Settings.System.getInt(mContext.getContentResolver(),
+                        Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0;
+                mCheckbox.setChecked(useLock);
+            } catch (SettingNotFoundException e) {
+            }
+        } else {
+            mCheckbox = null;
+        }
     }
 
     @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)
+            // Set whether to use the lock screen when exiting.
             Settings.System.putInt(mContext.getContentResolver(),
-                    Settings.System.LOCK_TO_APP_ENABLED, 1);
+                    Settings.System.LOCK_TO_APP_EXIT_LOCKED,
+                    mCheckbox != null && mCheckbox.isChecked() ? 1 : 0);
 
             // Start lock-to-app.
             mService.startLockTaskMode(mRequestedTask);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 2f25bd4..a20be73 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -83,7 +83,6 @@
     int pssProcState = -1;      // The proc state we are currently requesting pss for
     boolean serviceb;           // Process currently is on the service B list
     boolean serviceHighRam;     // We are forcing to service B list due to its RAM use
-    boolean keeping;            // Actively running code so don't kill due to that?
     boolean setIsForeground;    // Running foreground UI when last set?
     boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
     boolean hasClientActivities;  // Are there any client services with activities?
@@ -225,8 +224,7 @@
                 pw.print(" lruSeq="); pw.print(lruSeq);
                 pw.print(" lastPss="); pw.print(lastPss);
                 pw.print(" lastCachedPss="); pw.println(lastCachedPss);
-        pw.print(prefix); pw.print("keeping="); pw.print(keeping);
-                pw.print(" cached="); pw.print(cached);
+        pw.print(prefix); pw.print("cached="); pw.print(cached);
                 pw.print(" empty="); pw.println(empty);
         if (serviceb) {
             pw.print(prefix); pw.print("serviceb="); pw.print(serviceb);
@@ -275,16 +273,15 @@
         if (hasStartedServices) {
             pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
         }
-        if (!keeping) {
+        if (setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
             long wtime;
             synchronized (mBatteryStats) {
                 wtime = mBatteryStats.getProcessWakeTime(info.uid,
                         pid, SystemClock.elapsedRealtime());
             }
-            long timeUsed = wtime - lastWakeTime;
             pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
                     pw.print(" timeUsed=");
-                    TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+                    TimeUtils.formatDuration(wtime-lastWakeTime, pw); pw.println("");
             pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
                     pw.print(" timeUsed=");
                     TimeUtils.formatDuration(curCpuTime-lastCpuTime, pw); pw.println("");
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 6fb8570..545723a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -148,8 +148,8 @@
     /**
      * Request ConnectivityService display provisioning notification.
      * arg1    = Whether to make the notification visible.
-     * obj     = Intent to be launched when notification selected by user.
-     * replyTo = NetworkAgentInfo.messenger so ConnectivityService can identify sender.
+     * arg2    = NetID.
+     * obj     = Intent to be launched when notification selected by user, null if !arg1.
      */
     public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12;
 
@@ -447,9 +447,9 @@
             // Initiate notification to sign-in.
             Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED);
             intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, 0,
+            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
+                    mNetworkAgentInfo.network.netId,
                     PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
-            message.replyTo = mNetworkAgentInfo.messenger;
             mConnectivityServiceHandler.sendMessage(message);
         }
 
@@ -470,8 +470,8 @@
 
         @Override
         public void exit() {
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, 0, null);
-            message.replyTo = mNetworkAgentInfo.messenger;
+            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
+                    mNetworkAgentInfo.network.netId, null);
             mConnectivityServiceHandler.sendMessage(message);
             mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
             mUserRespondedBroadcastReceiver = null;
@@ -648,6 +648,7 @@
                         new InputStreamReader(socket.getInputStream()));
                 OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
                 writer.write("GET " + url.getFile() + " HTTP/1.1\r\nHost: " + url.getHost() +
+                        "\r\nUser-Agent: " + System.getProperty("http.agent") +
                         "\r\nConnection: close\r\n\r\n");
                 writer.flush();
                 String response = reader.readLine();
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 1b40cdf..08d6fc9 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1212,8 +1212,7 @@
             } else {
                 try {
                     mEventName = mSyncOperation.wakeLockName();
-                    mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_START,
-                            mEventName, mSyncAdapterUid);
+                    mBatteryStats.noteSyncStart(mEventName, mSyncAdapterUid);
                 } catch (RemoteException e) {
                 }
             }
@@ -1232,8 +1231,7 @@
                 mBound = false;
                 mContext.unbindService(this);
                 try {
-                    mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_FINISH,
-                            mEventName, mSyncAdapterUid);
+                    mBatteryStats.noteSyncFinish(mEventName, mSyncAdapterUid);
                 } catch (RemoteException e) {
                 }
             }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 7f8b232..caae9f5 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -35,16 +35,20 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.IdleController;
@@ -52,8 +56,6 @@
 import com.android.server.job.controllers.StateController;
 import com.android.server.job.controllers.TimeController;
 
-import java.util.LinkedList;
-
 /**
  * Responsible for taking jobs representing work to be performed by a client app, and determining
  * based on the criteria specified when that job should be run against the client application's
@@ -74,7 +76,7 @@
     private static final int MAX_JOB_CONTEXTS_COUNT = 3;
     static final String TAG = "JobManagerService";
     /** Master list of jobs. */
-    private final JobStore mJobs;
+    final JobStore mJobs;
 
     static final int MSG_JOB_EXPIRED = 0;
     static final int MSG_CHECK_JOB = 1;
@@ -84,33 +86,41 @@
      * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
      * early.
      */
-    private static final int MIN_IDLE_COUNT = 1;
+    static final int MIN_IDLE_COUNT = 1;
     /**
      * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
      * things early.
      */
-    private static final int MIN_CONNECTIVITY_COUNT = 2;
+    static final int MIN_CONNECTIVITY_COUNT = 2;
     /**
      * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
      * some work early.
      */
-    private static final int MIN_READY_JOBS_COUNT = 4;
+    static final int MIN_READY_JOBS_COUNT = 4;
 
     /**
      * Track Services that have currently active or pending jobs. The index is provided by
      * {@link JobStatus#getServiceToken()}
      */
-    private final List<JobServiceContext> mActiveServices = new LinkedList<JobServiceContext>();
+    final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>();
     /** List of controllers that will notify this service of updates to jobs. */
-    private List<StateController> mControllers;
+    List<StateController> mControllers;
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
      */
-    private final LinkedList<JobStatus> mPendingJobs = new LinkedList<JobStatus>();
+    final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>();
 
-    private final JobHandler mHandler;
-    private final JobSchedulerStub mJobSchedulerStub;
+    final JobHandler mHandler;
+    final JobSchedulerStub mJobSchedulerStub;
+
+    IBatteryStats mBatteryStats;
+
+    /**
+     * Set to true once we are allowed to run third party apps.
+     */
+    boolean mReadyToRock;
+
     /**
      * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
      * still clean up. On reinstall the package will have a new uid.
@@ -152,7 +162,9 @@
     public List<JobInfo> getPendingJobs(int uid) {
         ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
         synchronized (mJobs) {
-            for (JobStatus job : mJobs.getJobs()) {
+            ArraySet<JobStatus> jobs = mJobs.getJobs();
+            for (int i=0; i<jobs.size(); i++) {
+                JobStatus job = jobs.valueAt(i);
                 if (job.getUid() == uid) {
                     outList.add(job.getJob());
                 }
@@ -164,7 +176,8 @@
     private void cancelJobsForUser(int userHandle) {
         synchronized (mJobs) {
             List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
-            for (JobStatus toRemove : jobsForUser) {
+            for (int i=0; i<jobsForUser.size(); i++) {
+                JobStatus toRemove = jobsForUser.get(i);
                 if (DEBUG) {
                     Slog.d(TAG, "Cancelling: " + toRemove);
                 }
@@ -183,7 +196,8 @@
         // Remove from master list.
         synchronized (mJobs) {
             List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
-            for (JobStatus toRemove : jobsForUid) {
+            for (int i=0; i<jobsForUid.size(); i++) {
+                JobStatus toRemove = jobsForUid.get(i);
                 if (DEBUG) {
                     Slog.d(TAG, "Cancelling: " + toRemove);
                 }
@@ -230,7 +244,7 @@
     public JobSchedulerService(Context context) {
         super(context);
         // Create the controllers.
-        mControllers = new LinkedList<StateController>();
+        mControllers = new ArrayList<StateController>();
         mControllers.add(ConnectivityController.get(this));
         mControllers.add(TimeController.get(this));
         mControllers.add(IdleController.get(this));
@@ -238,11 +252,6 @@
 
         mHandler = new JobHandler(context.getMainLooper());
         mJobSchedulerStub = new JobSchedulerStub();
-        // Create the "runners".
-        for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
-            mActiveServices.add(
-                    new JobServiceContext(this, context.getMainLooper()));
-        }
         mJobs = JobStore.initAndGet(this);
     }
 
@@ -262,6 +271,29 @@
             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+        } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+            synchronized (mJobs) {
+                // Let's go!
+                mReadyToRock = true;
+                mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                        BatteryStats.SERVICE_NAME));
+                // Create the "runners".
+                for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
+                    mActiveServices.add(
+                            new JobServiceContext(this, mBatteryStats,
+                                    getContext().getMainLooper()));
+                }
+                // Attach jobs to their controllers.
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
+                    for (int j=0; j<mControllers.size(); j++) {
+                        mControllers.get(i).maybeStartTrackingJob(job);
+                    }
+                }
+                // GO GO GO!
+                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+            }
         }
     }
 
@@ -272,14 +304,19 @@
      */
     private void startTrackingJob(JobStatus jobStatus) {
         boolean update;
+        boolean rocking;
         synchronized (mJobs) {
             update = mJobs.add(jobStatus);
+            rocking = mReadyToRock;
         }
-        for (StateController controller : mControllers) {
-            if (update) {
-                controller.maybeStopTrackingJob(jobStatus);
+        if (rocking) {
+            for (int i=0; i<mControllers.size(); i++) {
+                StateController controller = mControllers.get(i);
+                if (update) {
+                    controller.maybeStopTrackingJob(jobStatus);
+                }
+                controller.maybeStartTrackingJob(jobStatus);
             }
-            controller.maybeStartTrackingJob(jobStatus);
         }
     }
 
@@ -289,12 +326,15 @@
      */
     private boolean stopTrackingJob(JobStatus jobStatus) {
         boolean removed;
+        boolean rocking;
         synchronized (mJobs) {
             // Remove from store as well as controllers.
             removed = mJobs.remove(jobStatus);
+            rocking = mReadyToRock;
         }
-        if (removed) {
-            for (StateController controller : mControllers) {
+        if (removed && rocking) {
+            for (int i=0; i<mControllers.size(); i++) {
+                StateController controller = mControllers.get(i);
                 controller.maybeStopTrackingJob(jobStatus);
             }
         }
@@ -302,7 +342,8 @@
     }
 
     private boolean stopJobOnServiceContextLocked(JobStatus job) {
-        for (JobServiceContext jsc : mActiveServices) {
+        for (int i=0; i<mActiveServices.size(); i++) {
+            JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus executing = jsc.getRunningJob();
             if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
                 jsc.cancelExecutingJob();
@@ -318,7 +359,8 @@
      * is pending.
      */
     private boolean isCurrentlyActiveLocked(JobStatus job) {
-        for (JobServiceContext serviceContext : mActiveServices) {
+        for (int i=0; i<mActiveServices.size(); i++) {
+            JobServiceContext serviceContext = mActiveServices.get(i);
             final JobStatus running = serviceContext.getRunningJob();
             if (running != null && running.matches(job.getUid(), job.getJobId())) {
                 return true;
@@ -431,8 +473,13 @@
      */
     @Override
     public void onControllerStateChanged() {
-        // Post a message to to run through the list of jobs and start/stop any that are eligible.
-        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+        synchronized (mJobs) {
+            if (mReadyToRock) {
+                // Post a message to to run through the list of jobs and start/stop any that
+                // are eligible.
+                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+            }
+        }
     }
 
     @Override
@@ -449,7 +496,8 @@
     @Override
     public void onJobMapReadFinished(List<JobStatus> jobs) {
         synchronized (mJobs) {
-            for (JobStatus js : jobs) {
+            for (int i=0; i<jobs.size(); i++) {
+                JobStatus js = jobs.get(i);
                 if (mJobs.containsJobIdForUid(js.getJobId(), js.getUid())) {
                     // An app with BOOT_COMPLETED *might* have decided to reschedule their job, in
                     // the same amount of time it took us to read it from disk. If this is the case
@@ -495,7 +543,9 @@
          */
         private void queueReadyJobsForExecutionH() {
             synchronized (mJobs) {
-                for (JobStatus job : mJobs.getJobs()) {
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
                     if (isReadyToBeExecutedLocked(job)) {
                         mPendingJobs.add(job);
                     } else if (isReadyToBeCancelledLocked(job)) {
@@ -520,7 +570,9 @@
                 int backoffCount = 0;
                 int connectivityCount = 0;
                 List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
-                for (JobStatus job : mJobs.getJobs()) {
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
                     if (isReadyToBeExecutedLocked(job)) {
                         if (job.getNumFailures() > 0) {
                             backoffCount++;
@@ -539,8 +591,8 @@
                 if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT ||
                         connectivityCount >= MIN_CONNECTIVITY_COUNT ||
                         runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
-                    for (JobStatus job : runnableJobs) {
-                        mPendingJobs.add(job);
+                    for (int i=0; i<runnableJobs.size(); i++) {
+                        mPendingJobs.add(runnableJobs.get(i));
                     }
                 }
             }
@@ -576,7 +628,8 @@
                 while (it.hasNext()) {
                     JobStatus nextPending = it.next();
                     JobServiceContext availableContext = null;
-                    for (JobServiceContext jsc : mActiveServices) {
+                    for (int i=0; i<mActiveServices.size(); i++) {
+                        JobServiceContext jsc = mActiveServices.get(i);
                         final JobStatus running = jsc.getRunningJob();
                         if (running != null && running.matches(nextPending.getUid(),
                                 nextPending.getJobId())) {
@@ -737,25 +790,28 @@
         synchronized (mJobs) {
             pw.println("Registered jobs:");
             if (mJobs.size() > 0) {
-                for (JobStatus job : mJobs.getJobs()) {
+                ArraySet<JobStatus> jobs = mJobs.getJobs();
+                for (int i=0; i<jobs.size(); i++) {
+                    JobStatus job = jobs.valueAt(i);
                     job.dump(pw, "  ");
                 }
             } else {
                 pw.println();
                 pw.println("No jobs scheduled.");
             }
-            for (StateController controller : mControllers) {
+            for (int i=0; i<mControllers.size(); i++) {
                 pw.println();
-                controller.dumpControllerState(pw);
+                mControllers.get(i).dumpControllerState(pw);
             }
             pw.println();
             pw.println("Pending");
-            for (JobStatus jobStatus : mPendingJobs) {
-                pw.println(jobStatus.hashCode());
+            for (int i=0; i<mPendingJobs.size(); i++) {
+                pw.println(mPendingJobs.get(i).hashCode());
             }
             pw.println();
             pw.println("Active jobs:");
-            for (JobServiceContext jsc : mActiveServices) {
+            for (int i=0; i<mActiveServices.size(); i++) {
+                JobServiceContext jsc = mActiveServices.get(i);
                 if (jsc.isAvailable()) {
                     continue;
                 } else {
@@ -765,6 +821,8 @@
                             "timeout: " + jsc.getTimeoutElapsed());
                 }
             }
+            pw.println();
+            pw.print("mReadyToRock="); pw.println(mReadyToRock);
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 534faba3..eaf5480 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
 import com.android.server.job.controllers.JobStatus;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,8 +58,6 @@
     private static final long EXECUTING_TIMESLICE_MILLIS = 60 * 1000;
     /** Amount of time the JobScheduler will wait for a response from an app for a message. */
     private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
-    /** String prefix for all wakelock names. */
-    private static final String JS_WAKELOCK_PREFIX = "*job*/";
 
     private static final String[] VERB_STRINGS = {
             "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_PENDING"
@@ -87,6 +86,7 @@
     private final JobCompletedListener mCompletedListener;
     /** Used for service binding, etc. */
     private final Context mContext;
+    private final IBatteryStats mBatteryStats;
     private PowerManager.WakeLock mWakeLock;
 
     // Execution state.
@@ -109,13 +109,15 @@
     /** Track when job will timeout. */
     private long mTimeoutElapsed;
 
-    JobServiceContext(JobSchedulerService service, Looper looper) {
-        this(service.getContext(), service, looper);
+    JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) {
+        this(service.getContext(), batteryStats, service, looper);
     }
 
     @VisibleForTesting
-    JobServiceContext(Context context, JobCompletedListener completedListener, Looper looper) {
+    JobServiceContext(Context context, IBatteryStats batteryStats,
+            JobCompletedListener completedListener, Looper looper) {
         mContext = context;
+        mBatteryStats = batteryStats;
         mCallbackHandler = new JobServiceHandler(looper);
         mCompletedListener = completedListener;
         mAvailable = true;
@@ -152,6 +154,11 @@
                 mExecutionStartTimeElapsed = 0L;
                 return false;
             }
+            try {
+                mBatteryStats.noteJobStart(job.getName(), job.getUid());
+            } catch (RemoteException e) {
+                // Whatever.
+            }
             mAvailable = false;
             return true;
         }
@@ -228,8 +235,7 @@
         mCallbackHandler.removeMessages(MSG_TIMEOUT);
         final PowerManager pm =
                 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                JS_WAKELOCK_PREFIX + mRunningJob.getServiceComponent().getPackageName());
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag());
         mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
         mWakeLock.setReferenceCounted(false);
         mWakeLock.acquire();
@@ -483,6 +489,11 @@
             removeMessages(MSG_TIMEOUT);
             mCompletedListener.onJobCompleted(mRunningJob, reschedule);
             synchronized (mLock) {
+                try {
+                    mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getUid());
+                } catch (RemoteException e) {
+                    // Whatever.
+                }
                 mWakeLock.release();
                 mContext.unbindService(JobServiceContext.this);
                 mWakeLock = null;
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 8736980..48312b0 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -136,7 +136,8 @@
      * Whether this jobStatus object already exists in the JobStore.
      */
     public boolean containsJobIdForUid(int jobId, int uId) {
-        for (JobStatus ts : mJobSet) {
+        for (int i=mJobSet.size()-1; i>=0; i--) {
+            JobStatus ts = mJobSet.valueAt(i);
             if (ts.getUid() == uId && ts.getJobId() == jobId) {
                 return true;
             }
@@ -267,7 +268,8 @@
             List<JobStatus> mStoreCopy = new ArrayList<JobStatus>();
             synchronized (JobStore.this) {
                 // Copy over the jobs so we can release the lock before writing.
-                for (JobStatus jobStatus : mJobSet) {
+                for (int i=0; i<mJobSet.size(); i++) {
+                    JobStatus jobStatus = mJobSet.valueAt(i);
                     JobStatus copy = new JobStatus(jobStatus.getJob(), jobStatus.getUid(),
                             jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed());
                     mStoreCopy.add(copy);
@@ -290,7 +292,8 @@
 
                 out.startTag(null, "job-info");
                 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
-                for (JobStatus jobStatus : jobList) {
+                for (int i=0; i<jobList.size(); i++) {
+                    JobStatus jobStatus = jobList.get(i);
                     if (DEBUG) {
                         Slog.d(TAG, "Saving job " + jobStatus.getJobId());
                     }
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 9ee2869..652d8f8 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -42,6 +42,8 @@
 
     final JobInfo job;
     final int uId;
+    final String name;
+    final String tag;
 
     // Constraints.
     final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
@@ -72,6 +74,8 @@
     private JobStatus(JobInfo job, int uId, int numFailures) {
         this.job = job;
         this.uId = uId;
+        this.name = job.getService().flattenToShortString();
+        this.tag = "*job*/" + this.name;
         this.numFailures = numFailures;
     }
 
@@ -140,6 +144,14 @@
         return uId;
     }
 
+    public String getName() {
+        return name;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
     public PersistableBundle getExtras() {
         return job.getExtras();
     }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 01a21f4..ede3dab 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -203,39 +203,32 @@
     }
 
     /**
-     * Send a volume adjustment to the session owner.
+     * Send a volume adjustment to the session owner. Direction must be one of
+     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+     * {@link AudioManager#ADJUST_SAME}.
      *
-     * @param delta The amount to adjust the volume by.
+     * @param direction The direction to adjust volume in.
      */
-    public void adjustVolumeBy(int delta, int flags) {
+    public void adjustVolume(int direction, int flags) {
         if (isPlaybackActive(false)) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
+        if (direction > 1) {
+            direction = 1;
+        } else if (direction < -1) {
+            direction = -1;
+        }
         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);
-                }
-            }
+            mAudioManager.adjustStreamVolume(mAudioStream, direction, flags);
         } else {
             if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
                 // Nothing to do, the volume cannot be changed
                 return;
             }
-            mSessionCb.adjustVolumeBy(delta);
+            mSessionCb.adjustVolume(direction);
 
             int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
-            mOptimisticVolume = volumeBefore + delta;
+            mOptimisticVolume = volumeBefore + direction;
             mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
             mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
             mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
@@ -752,11 +745,11 @@
             }
         }
 
-        public void adjustVolumeBy(int delta) {
+        public void adjustVolume(int direction) {
             try {
-                mCb.onAdjustVolumeBy(delta);
+                mCb.onAdjustVolume(direction);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+                Slog.e(TAG, "Remote failure in adjustVolume.", e);
             }
         }
 
@@ -764,7 +757,7 @@
             try {
                 mCb.onSetVolumeTo(value);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+                Slog.e(TAG, "Remote failure in setVolumeTo.", e);
             }
         }
     }
@@ -838,10 +831,10 @@
         }
 
         @Override
-        public void adjustVolumeBy(int delta, int flags) {
+        public void adjustVolume(int direction, int flags) {
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+                MediaSessionRecord.this.adjustVolume(direction, flags);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 4c475d9..a2dd15e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -681,7 +681,7 @@
         }
 
         @Override
-        public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
+        public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
                 throws RemoteException {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
@@ -690,7 +690,7 @@
                 synchronized (mLock) {
                     MediaSessionRecord session = mPriorityStack
                             .getDefaultVolumeSession(mCurrentUserId);
-                    dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
+                    dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -764,11 +764,11 @@
             return resolvedUserId;
         }
 
-        private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
+        private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
                 MediaSessionRecord session) {
             if (DEBUG) {
                 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
-                Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
+                Log.d(TAG, "Adjusting session " + sessionInfo + " by " + direction + ". flags=" + flags
                         + ", suggestedStream=" + suggestedStream);
 
             }
@@ -780,28 +780,13 @@
                     }
                 }
                 try {
-                    if (delta == 0) {
-                        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());
-                        }
-                    }
+                    mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
+                            getContext().getOpPackageName());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error adjusting default volume.", e);
                 }
             } else {
-                session.adjustVolumeBy(delta, flags);
+                session.adjustVolume(direction, flags);
                 if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
                         && mRvc != null) {
                     try {
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 08c8271..f4248da 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -53,6 +53,7 @@
     private final CountdownConditionProvider mCountdown = new CountdownConditionProvider();
 
     private Uri mExitConditionId;
+    private ComponentName mExitConditionComponent;
 
     public ConditionProviders(Context context, Handler handler,
             UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
@@ -95,6 +96,7 @@
                 }
             }
         }
+        mCountdown.dump(pw, filter);
     }
 
     @Override
@@ -120,20 +122,20 @@
             // we tried
         }
         synchronized (mMutex) {
+            if (info.component.equals(mExitConditionComponent)) {
+                // ensure record exists, we'll wire it up and subscribe below
+                final ConditionRecord manualRecord =
+                        getRecordLocked(mExitConditionId, mExitConditionComponent);
+                manualRecord.isManual = true;
+            }
             final int N = mRecords.size();
             for(int i = 0; i < N; i++) {
                 final ConditionRecord r = mRecords.get(i);
                 if (!r.component.equals(info.component)) continue;
                 r.info = info;
-                // if automatic, auto-subscribe
-                if (r.isAutomatic) {
-                    try {
-                        final Uri id = r.id;
-                        if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id);
-                        provider.onSubscribe(id);
-                    } catch (RemoteException e) {
-                        // we tried
-                    }
+                // if automatic or manual, auto-subscribe
+                if (r.isAutomatic || r.isManual) {
+                    subscribeLocked(r);
                 }
             }
         }
@@ -274,6 +276,7 @@
     public void setZenModeCondition(Uri conditionId) {
         if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
         synchronized(mMutex) {
+            ComponentName conditionComponent = null;
             if (ZenModeConfig.isValidCountdownConditionId(conditionId)) {
                 // constructed by the client, make sure the record exists...
                 final ConditionRecord r = getRecordLocked(conditionId,
@@ -296,9 +299,13 @@
                     subscribeLocked(r);
                     r.isManual = true;
                 }
+                if (idEqual) {
+                    conditionComponent = r.component;
+                }
             }
             if (!Objects.equals(mExitConditionId, conditionId)) {
                 mExitConditionId = conditionId;
+                mExitConditionComponent = conditionComponent;
                 saveZenConfigLocked();
             }
         }
@@ -404,6 +411,13 @@
         for (ManagedServiceInfo info : mServices) {
             final IConditionProvider provider = provider(info);
             if (provider == null) continue;
+            // clear all stored conditions from this provider that we no longer care about
+            for (int i = mRecords.size() - 1; i >= 0; i--) {
+                final ConditionRecord r = mRecords.get(i);
+                if (r.info != info) continue;
+                if (r.isManual || r.isAutomatic) continue;
+                mRecords.remove(i);
+            }
             try {
                 provider.onRequestConditions(flags);
             } catch (RemoteException e) {
@@ -420,6 +434,7 @@
         }
         synchronized (mMutex) {
             mExitConditionId = config.exitConditionId;
+            mExitConditionComponent = config.exitConditionComponent;
             if (config.conditionComponents == null || config.conditionIds == null
                     || config.conditionComponents.length != config.conditionIds.length) {
                 if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
@@ -467,6 +482,7 @@
             }
         }
         config.exitConditionId = mExitConditionId;
+        config.exitConditionComponent = mExitConditionComponent;
         if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
         mZenModeHelper.setConfig(config);
     }
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index 0884f76..aaf7cfc 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -31,6 +31,9 @@
 import android.text.format.DateUtils;
 import android.util.Slog;
 
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
 import java.util.Date;
 
 /** Built-in zen condition provider for simple time-based conditions */
@@ -49,11 +52,18 @@
     private final Receiver mReceiver = new Receiver();
 
     private boolean mConnected;
+    private long mTime;
 
     public CountdownConditionProvider() {
         if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()");
     }
 
+    public void dump(PrintWriter pw, DumpFilter filter) {
+        pw.println("    CountdownConditionProvider:");
+        pw.print("      mConnected="); pw.println(mConnected);
+        pw.print("      mTime="); pw.println(mTime);
+    }
+
     @Override
     public void onConnected() {
         if (DEBUG) Slog.d(TAG, "onConnected");
@@ -79,7 +89,7 @@
     @Override
     public void onSubscribe(Uri conditionId) {
         if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
-        final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
+        mTime = ZenModeConfig.tryParseCountdownConditionId(conditionId);
         final AlarmManager alarms = (AlarmManager)
                 mContext.getSystemService(Context.ALARM_SERVICE);
         final Intent intent = new Intent(ACTION).putExtra(EXTRA_CONDITION_ID, conditionId)
@@ -87,14 +97,21 @@
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
                 intent, PendingIntent.FLAG_UPDATE_CURRENT);
         alarms.cancel(pendingIntent);
-        if (time > 0) {
+        if (mTime > 0) {
             final long now = System.currentTimeMillis();
             final CharSequence span =
-                    DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
+                    DateUtils.getRelativeTimeSpanString(mTime, now, DateUtils.MINUTE_IN_MILLIS);
+            if (mTime <= now) {
+                // in the past, already false
+                notifyCondition(newCondition(mTime, Condition.STATE_FALSE));
+            } else {
+                // in the future, set an alarm
+                alarms.setExact(AlarmManager.RTC_WAKEUP, mTime, pendingIntent);
+            }
             if (DEBUG) Slog.d(TAG, String.format(
-                    "Scheduling %s for %s, %s in the future (%s), now=%s",
-                    ACTION, ts(time), time - now, span, ts(now)));
-            alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+                    "%s %s for %s, %s in the future (%s), now=%s",
+                    (mTime <= now ? "Not scheduling" : "Scheduling"),
+                    ACTION, ts(mTime), mTime - now, span, ts(now)));
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 6cd4019..0546a55 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -28,6 +28,12 @@
         if (lhs.isRecentlyIntrusive() != rhs.isRecentlyIntrusive()) {
             return lhs.isRecentlyIntrusive() ? -1 : 1;
         }
+        final int leftPackagePriority = lhs.getPackagePriority();
+        final int rightPackagePriority = rhs.getPackagePriority();
+        if (leftPackagePriority != rightPackagePriority) {
+            // by priority, high to low
+            return -1 * Integer.compare(leftPackagePriority, rightPackagePriority);
+        }
         final int leftScore = lhs.sbn.getScore();
         final int rightScore = rhs.sbn.getScore();
         if (leftScore != rightScore) {
diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
index d8ab9d7..1335706 100644
--- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
@@ -63,4 +63,9 @@
             }
         };
     }
-}
\ No newline at end of file
+
+    @Override
+    public void setConfig(RankingConfig config) {
+        // ignore: config has no relevant information yet.
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 892f61f..b15ac8d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -103,11 +103,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
-import java.util.concurrent.TimeUnit;
 
 /** {@hide} */
 public class NotificationManagerService extends SystemService {
@@ -120,7 +118,8 @@
     static final int MESSAGE_TIMEOUT = 2;
     static final int MESSAGE_SAVE_POLICY_FILE = 3;
     static final int MESSAGE_RECONSIDER_RANKING = 4;
-    static final int MESSAGE_SEND_RANKING_UPDATE = 5;
+    static final int MESSAGE_RANKING_CONFIG_CHANGE = 5;
+    static final int MESSAGE_SEND_RANKING_UPDATE = 6;
 
     static final int LONG_DELAY = 3500; // 3.5 seconds
     static final int SHORT_DELAY = 2000; // 2 seconds
@@ -152,7 +151,6 @@
     private WorkerHandler mHandler;
     private final HandlerThread mRankingThread = new HandlerThread("ranker",
             Process.THREAD_PRIORITY_BACKGROUND);
-    private Handler mRankingHandler = null;
 
     private Light mNotificationLight;
     Light mAttentionLight;
@@ -178,7 +176,6 @@
     // used as a mutex for access to all active notifications & listeners
     final ArrayList<NotificationRecord> mNotificationList =
             new ArrayList<NotificationRecord>();
-    final NotificationComparator mRankingComparator = new NotificationComparator();
     final ArrayMap<String, NotificationRecord> mNotificationsByKey =
             new ArrayMap<String, NotificationRecord>();
     final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
@@ -203,7 +200,7 @@
     private static final String TAG_PACKAGE = "package";
     private static final String ATTR_NAME = "name";
 
-    final ArrayList<NotificationSignalExtractor> mSignalExtractors = new ArrayList<NotificationSignalExtractor>();
+    private RankingHelper mRankingHelper;
 
     private final UserProfiles mUserProfiles = new UserProfiles();
     private NotificationListeners mListeners;
@@ -360,6 +357,7 @@
                         }
                     }
                     mZenModeHelper.readXml(parser);
+                    mRankingHelper.readXml(parser);
                 }
             } catch (FileNotFoundException e) {
                 // No data yet
@@ -398,6 +396,7 @@
                 out.startTag(null, TAG_BODY);
                 out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
                 mZenModeHelper.writeXml(out);
+                mRankingHelper.writeXml(out);
                 out.endTag(null, TAG_BODY);
                 out.endDocument();
                 mPolicyFile.finishWrite(stream);
@@ -752,13 +751,23 @@
 
     @Override
     public void onStart() {
+        Resources resources = getContext().getResources();
+
         mAm = ActivityManagerNative.getDefault();
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
 
         mHandler = new WorkerHandler();
         mRankingThread.start();
-        mRankingHandler = new RankingWorkerHandler(mRankingThread.getLooper());
+        String[] extractorNames;
+        try {
+            extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
+        } catch (Resources.NotFoundException e) {
+            extractorNames = new String[0];
+        }
+        mRankingHelper = new RankingHelper(getContext(),
+                new RankingWorkerHandler(mRankingThread.getLooper()),
+                extractorNames);
         mZenModeHelper = new ZenModeHelper(getContext(), mHandler);
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
@@ -782,7 +791,6 @@
         mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
         mAttentionLight = lights.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
-        Resources resources = getContext().getResources();
         mDefaultNotificationColor = resources.getColor(
                 R.color.config_defaultNotificationColor);
         mDefaultNotificationLedOn = resources.getInteger(
@@ -837,25 +845,6 @@
 
         mSettingsObserver = new SettingsObserver(mHandler);
 
-        // spin up NotificationSignalExtractors
-        String[] extractorNames = resources.getStringArray(
-                R.array.config_notificationSignalExtractors);
-        for (String extractorName : extractorNames) {
-            try {
-                Class<?> extractorClass = getContext().getClassLoader().loadClass(extractorName);
-                NotificationSignalExtractor extractor =
-                        (NotificationSignalExtractor) extractorClass.newInstance();
-                extractor.initialize(getContext());
-                mSignalExtractors.add(extractor);
-            } catch (ClassNotFoundException e) {
-                Slog.w(TAG, "Couldn't find extractor " + extractorName + ".", e);
-            } catch (InstantiationException e) {
-                Slog.w(TAG, "Couldn't instantiate extractor " + extractorName + ".", e);
-            } catch (IllegalAccessException e) {
-                Slog.w(TAG, "Problem accessing extractor " + extractorName + ".", e);
-            }
-        }
-
         mArchive = new Archive(resources.getInteger(
                 R.integer.config_notificationServiceArchiveSize));
 
@@ -1062,6 +1051,19 @@
                     == AppOpsManager.MODE_ALLOWED);
         }
 
+        @Override
+        public void setPackagePriority(String pkg, int uid, int priority) {
+            checkCallerIsSystem();
+            mRankingHelper.setPackagePriority(pkg, uid, priority);
+            savePolicyFile();
+        }
+
+        @Override
+        public int getPackagePriority(String pkg, int uid) {
+            checkCallerIsSystem();
+            return mRankingHelper.getPackagePriority(pkg, uid);
+        }
+
         /**
          * System-only API for getting a list of current (i.e. not cleared) notifications.
          *
@@ -1338,72 +1340,84 @@
     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.print(" (filtered to "); pw.print(filter); pw.print(")");
         }
         pw.println(':');
         int N;
+        final boolean zenOnly = filter != null && filter.zen;
 
-        synchronized (mToastQueue) {
-            N = mToastQueue.size();
-            if (N > 0) {
-                pw.println("  Toast Queue:");
-                for (int i=0; i<N; i++) {
-                    mToastQueue.get(i).dump(pw, "    ", filter);
+        if (!zenOnly) {
+            synchronized (mToastQueue) {
+                N = mToastQueue.size();
+                if (N > 0) {
+                    pw.println("  Toast Queue:");
+                    for (int i=0; i<N; i++) {
+                        mToastQueue.get(i).dump(pw, "    ", filter);
+                    }
+                    pw.println("  ");
                 }
-                pw.println("  ");
             }
         }
 
         synchronized (mNotificationList) {
-            N = mNotificationList.size();
-            if (N > 0) {
-                pw.println("  Notification List:");
-                for (int i=0; i<N; i++) {
-                    final NotificationRecord nr = mNotificationList.get(i);
-                    if (filter != null && !filter.matches(nr.sbn)) continue;
-                    nr.dump(pw, "    ", getContext());
-                }
-                pw.println("  ");
-            }
-
-            if (filter == null) {
-                N = mLights.size();
+            if (!zenOnly) {
+                N = mNotificationList.size();
                 if (N > 0) {
-                    pw.println("  Lights List:");
+                    pw.println("  Notification List:");
                     for (int i=0; i<N; i++) {
-                        pw.println("    " + mLights.get(i));
+                        final NotificationRecord nr = mNotificationList.get(i);
+                        if (filter != null && !filter.matches(nr.sbn)) continue;
+                        nr.dump(pw, "    ", getContext());
                     }
                     pw.println("  ");
                 }
 
-                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()) {
-                final StatusBarNotification sbn = iter.next();
-                if (filter != null && !filter.matches(sbn)) continue;
-                pw.println("    " + sbn);
-                if (++i >= 5) {
-                    if (iter.hasNext()) pw.println("    ...");
-                    break;
+                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("  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()) {
+                    final StatusBarNotification sbn = iter.next();
+                    if (filter != null && !filter.matches(sbn)) continue;
+                    pw.println("    " + sbn);
+                    if (++i >= 5) {
+                        if (iter.hasNext()) pw.println("    ...");
+                        break;
+                    }
                 }
             }
 
-            pw.println("\n  Usage Stats:");
-            mUsageStats.dump(pw, "    ", filter);
+            if (!zenOnly) {
+                pw.println("\n  Usage Stats:");
+                mUsageStats.dump(pw, "    ", filter);
+            }
 
-            if (filter == null) {
+            if (filter == null || zenOnly) {
                 pw.println("\n  Zen Mode:");
                 mZenModeHelper.dump(pw, "    ");
             }
 
-            pw.println("\n  Notification listeners:");
-            mListeners.dump(pw, filter);
+            if (!zenOnly) {
+                pw.println("\n  Ranking Config:");
+                mRankingHelper.dump(pw, "    ", filter);
+
+                pw.println("\n  Notification listeners:");
+                mListeners.dump(pw, filter);
+            }
 
             pw.println("\n  Condition providers:");
             mConditionProviders.dump(pw, filter);
@@ -1509,16 +1523,7 @@
                     // Retain ranking information from previous record
                     r.copyRankingInformation(old);
                 }
-                if (!mSignalExtractors.isEmpty()) {
-                    for (NotificationSignalExtractor extractor : mSignalExtractors) {
-                        try {
-                            RankingReconsideration recon = extractor.process(r);
-                            scheduleRankingReconsideration(recon);
-                        } catch (Throwable t) {
-                            Slog.w(TAG, "NotificationSignalExtractor failed.", t);
-                        }
-                    }
-                }
+                mRankingHelper.extractSignals(r);
 
                 // 3. Apply local rules
 
@@ -1563,7 +1568,7 @@
 
                     applyZenModeLocked(r);
 
-                    Collections.sort(mNotificationList, mRankingComparator);
+                    mRankingHelper.sort(mNotificationList);
 
                     if (notification.icon != 0) {
                         mListeners.notifyPostedLocked(n);
@@ -1838,14 +1843,6 @@
         }
     }
 
-    private void scheduleRankingReconsideration(RankingReconsideration recon) {
-        if (recon != null) {
-            Message m = Message.obtain(mRankingHandler, MESSAGE_RECONSIDER_RANKING, recon);
-            long delay = recon.getDelay(TimeUnit.MILLISECONDS);
-            mRankingHandler.sendMessageDelayed(m, delay);
-        }
-    }
-
     private void handleRankingReconsideration(Message message) {
         if (!(message.obj instanceof RankingReconsideration)) return;
         RankingReconsideration recon = (RankingReconsideration) message.obj;
@@ -1860,7 +1857,7 @@
             boolean interceptBefore = record.isIntercepted();
             recon.applyChangesLocked(record);
             applyZenModeLocked(record);
-            Collections.sort(mNotificationList, mRankingComparator);
+            mRankingHelper.sort(mNotificationList);
             int indexAfter = findNotificationRecordIndexLocked(record);
             boolean interceptAfter = record.isIntercepted();
             changed = indexBefore != indexAfter || interceptBefore != interceptAfter;
@@ -1873,6 +1870,25 @@
         }
     }
 
+    private void handleRankingConfigChange() {
+        synchronized (mNotificationList) {
+            final int N = mNotificationList.size();
+            ArrayList<String> orderBefore = new ArrayList<String>(N);
+            for (int i = 0; i < N; i++) {
+                final NotificationRecord r = mNotificationList.get(i);
+                orderBefore.add(r.getKey());
+                mRankingHelper.extractSignals(r);
+            }
+            mRankingHelper.sort(mNotificationList);
+            for (int i = 0; i < N; i++) {
+                if (!orderBefore.get(i).equals(mNotificationList.get(i).getKey())) {
+                    scheduleSendRankingUpdate();
+                    return;
+                }
+            }
+        }
+    }
+
     // let zen mode evaluate this record
     private void applyZenModeLocked(NotificationRecord record) {
         record.setIntercepted(mZenModeHelper.shouldIntercept(record));
@@ -1880,7 +1896,7 @@
 
     // lock on mNotificationList
     private int findNotificationRecordIndexLocked(NotificationRecord target) {
-        return Collections.binarySearch(mNotificationList, target, mRankingComparator);
+        return mRankingHelper.indexOf(mNotificationList, target);
     }
 
     private void scheduleSendRankingUpdate() {
@@ -1928,6 +1944,9 @@
                 case MESSAGE_RECONSIDER_RANKING:
                     handleRankingReconsideration(msg);
                     break;
+                case MESSAGE_RANKING_CONFIG_CHANGE:
+                    handleRankingConfigChange();
+                    break;
             }
         }
     }
@@ -2461,27 +2480,39 @@
 
     public static final class DumpFilter {
         public String pkgFilter;
+        public boolean zen;
 
         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;
+            if (args != null && args.length == 2 && "p".equals(args[0])
+                    && args[1] != null && !args[1].trim().isEmpty()) {
+                final DumpFilter filter = new DumpFilter();
+                filter.pkgFilter = args[1].trim().toLowerCase();
+                return filter;
             }
-            final DumpFilter filter = new DumpFilter();
-            filter.pkgFilter = args[1].trim().toLowerCase();
-            return filter;
+            if (args != null && args.length == 1 && "zen".equals(args[0])) {
+                final DumpFilter filter = new DumpFilter();
+                filter.zen = true;
+                return filter;
+            }
+            return null;
         }
 
         public boolean matches(StatusBarNotification sbn) {
-            return sbn != null && (matches(sbn.getPackageName()) || matches(sbn.getOpPkg()));
+            return zen ? true : sbn != null
+                    && (matches(sbn.getPackageName()) || matches(sbn.getOpPkg()));
         }
 
         public boolean matches(ComponentName component) {
-            return component != null && matches(component.getPackageName());
+            return zen ? true : component != null && matches(component.getPackageName());
         }
 
         public boolean matches(String pkg) {
-            return pkg != null && pkg.toLowerCase().contains(pkgFilter);
+            return zen ? true : pkg != null && pkg.toLowerCase().contains(pkgFilter);
+        }
+
+        @Override
+        public String toString() {
+            return zen ? "zen" : ('\'' + pkgFilter + '\'');
         }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0e6265c..088b813 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -58,6 +58,7 @@
 
     // Is this record an update of an old record?
     public boolean isUpdate;
+    private int mPackagePriority;
 
     NotificationRecord(StatusBarNotification sbn, int score)
     {
@@ -70,6 +71,7 @@
     public void copyRankingInformation(NotificationRecord previous) {
         mContactAffinity = previous.mContactAffinity;
         mRecentlyIntrusive = previous.mRecentlyIntrusive;
+        mPackagePriority = previous.mPackagePriority;
         mIntercept = previous.mIntercept;
         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
     }
@@ -141,6 +143,7 @@
         pw.println(prefix + "  stats=" + stats.toString());
         pw.println(prefix + "  mContactAffinity=" + mContactAffinity);
         pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
+        pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
         pw.println(prefix + "  mIntercept=" + mIntercept);
         pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
     }
@@ -193,6 +196,14 @@
         return mRecentlyIntrusive;
     }
 
+    public void setPackagePriority(int packagePriority) {
+      mPackagePriority = packagePriority;
+    }
+
+    public int getPackagePriority() {
+        return mPackagePriority;
+    }
+
     public boolean setIntercepted(boolean intercept) {
         mIntercept = intercept;
         return mIntercept;
@@ -230,5 +241,4 @@
         }
         return sbn.getPostTime();
     }
-
 }
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index 1537ea9..43d05d0 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -38,4 +38,10 @@
      */
     public RankingReconsideration process(NotificationRecord notification);
 
+    /**
+     * Called whenever the {@link RankingConfig} changes.
+     *
+     * @param config information about which signals are important.
+     */
+    void setConfig(RankingConfig config);
 }
diff --git a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java b/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
new file mode 100644
index 0000000..9cdb3e1
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
@@ -0,0 +1,54 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.content.Context;
+import android.util.Slog;
+
+public class PackagePriorityExtractor implements NotificationSignalExtractor {
+    private static final String TAG = "ImportantPackageExtractor";
+    private static final boolean DBG = false;
+
+    private RankingConfig mConfig;
+
+    public void initialize(Context ctx) {
+        if (DBG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
+    }
+
+    public RankingReconsideration process(NotificationRecord record) {
+        if (record == null || record.getNotification() == null) {
+            if (DBG) Slog.d(TAG, "skipping empty notification");
+            return null;
+        }
+
+        if (mConfig == null) {
+            if (DBG) Slog.d(TAG, "missing config");
+            return null;
+        }
+
+        final int packagePriority = mConfig.getPackagePriority(
+                record.sbn.getPackageName(), record.sbn.getUid());
+        record.setPackagePriority(packagePriority);
+
+        return null;
+    }
+
+    @Override
+    public void setConfig(RankingConfig config) {
+        mConfig = config;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
new file mode 100644
index 0000000..7d0bd59
--- /dev/null
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+public interface RankingConfig {
+    int getPackagePriority(String packageName, int uid);
+
+    void setPackagePriority(String packageName, int uid, int priority);
+}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
new file mode 100644
index 0000000..fc03c17
--- /dev/null
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+public class RankingHelper implements RankingConfig {
+    private static final String TAG = "RankingHelper";
+    private static final boolean DEBUG = false;
+
+    private static final int XML_VERSION = 1;
+
+    private static final String TAG_RANKING = "ranking";
+    private static final String TAG_PACKAGE = "package";
+    private static final String ATT_VERSION = "version";
+
+    private static final String ATT_NAME = "name";
+    private static final String ATT_UID = "uid";
+    private static final String ATT_PRIORITY = "priority";
+
+    private static final String VALUE_HIGH = "high";
+
+    private final NotificationSignalExtractor[] mSignalExtractors;
+    private final NotificationComparator mRankingComparator = new NotificationComparator();
+
+    // Package name to uid, to priority. Would be better as Table<String, Int, Int>
+    private final ArrayMap<String, SparseIntArray> mPackagePriorities;
+
+    private final Context mContext;
+    private final Handler mRankingHandler;
+
+    public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) {
+        mContext = context;
+        mRankingHandler = rankingHandler;
+        mPackagePriorities = new ArrayMap<String, SparseIntArray>();
+
+        final int N = extractorNames.length;
+        mSignalExtractors = new NotificationSignalExtractor[N];
+        for (int i = 0; i < N; i++) {
+            try {
+                Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
+                NotificationSignalExtractor extractor =
+                        (NotificationSignalExtractor) extractorClass.newInstance();
+                extractor.initialize(mContext);
+                extractor.setConfig(this);
+                mSignalExtractors[i] = extractor;
+            } catch (ClassNotFoundException e) {
+                Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
+            } catch (InstantiationException e) {
+                Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
+            } catch (IllegalAccessException e) {
+                Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
+            }
+        }
+    }
+
+    public void extractSignals(NotificationRecord r) {
+        final int N = mSignalExtractors.length;
+        for (int i = 0; i < N; i++) {
+            NotificationSignalExtractor extractor = mSignalExtractors[i];
+            try {
+                RankingReconsideration recon = extractor.process(r);
+                if (recon != null) {
+                    Message m = Message.obtain(mRankingHandler,
+                            NotificationManagerService.MESSAGE_RECONSIDER_RANKING, recon);
+                    long delay = recon.getDelay(TimeUnit.MILLISECONDS);
+                    mRankingHandler.sendMessageDelayed(m, delay);
+                }
+            } catch (Throwable t) {
+                Slog.w(TAG, "NotificationSignalExtractor failed.", t);
+            }
+        }
+    }
+
+    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+        int type = parser.getEventType();
+        if (type != XmlPullParser.START_TAG) return;
+        String tag = parser.getName();
+        if (!TAG_RANKING.equals(tag)) return;
+        mPackagePriorities.clear();
+        final int version = safeInt(parser, ATT_VERSION, XML_VERSION);
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            tag = parser.getName();
+            if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
+                return;
+            }
+            if (type == XmlPullParser.START_TAG) {
+                if (TAG_PACKAGE.equals(tag)) {
+                    int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
+                    int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT);
+                    String name = parser.getAttributeValue(null, ATT_NAME);
+
+                    if (!TextUtils.isEmpty(name) && priority != Notification.PRIORITY_DEFAULT) {
+                        SparseIntArray priorityByUid = mPackagePriorities.get(name);
+                        if (priorityByUid == null) {
+                            priorityByUid = new SparseIntArray();
+                            mPackagePriorities.put(name, priorityByUid);
+                        }
+                        priorityByUid.put(uid, priority);
+                    }
+                }
+            }
+        }
+        throw new IllegalStateException("Failed to reach END_DOCUMENT");
+    }
+
+    public void writeXml(XmlSerializer out) throws IOException {
+        out.startTag(null, TAG_RANKING);
+        out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
+
+        final int N = mPackagePriorities.size();
+        for (int i = 0; i < N; i ++) {
+            String name = mPackagePriorities.keyAt(i);
+            SparseIntArray priorityByUid = mPackagePriorities.get(name);
+            final int M = priorityByUid.size();
+            for (int j = 0; j < M; j++) {
+                int uid = priorityByUid.keyAt(j);
+                int priority = priorityByUid.get(uid);
+                out.startTag(null, TAG_PACKAGE);
+                out.attribute(null, ATT_NAME, name);
+                out.attribute(null, ATT_UID, Integer.toString(uid));
+                out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
+                out.endTag(null, TAG_PACKAGE);
+            }
+        }
+        out.endTag(null, TAG_RANKING);
+    }
+
+    private void updateConfig() {
+        final int N = mSignalExtractors.length;
+        for (int i = 0; i < N; i++) {
+            mSignalExtractors[i].setConfig(this);
+        }
+        mRankingHandler.sendEmptyMessage(NotificationManagerService.MESSAGE_RANKING_CONFIG_CHANGE);
+    }
+
+    public void sort(ArrayList<NotificationRecord> notificationList) {
+        Collections.sort(notificationList, mRankingComparator);
+    }
+
+    public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
+        return Collections.binarySearch(notificationList, target, mRankingComparator);
+    }
+
+    private static int safeInt(XmlPullParser parser, String att, int defValue) {
+        final String val = parser.getAttributeValue(null, att);
+        return tryParseInt(val, defValue);
+    }
+
+    private static int tryParseInt(String value, int defValue) {
+        if (TextUtils.isEmpty(value)) return defValue;
+        try {
+            return Integer.valueOf(value);
+        } catch (NumberFormatException e) {
+            return defValue;
+        }
+    }
+
+    @Override
+    public int getPackagePriority(String packageName, int uid) {
+        int priority = Notification.PRIORITY_DEFAULT;
+        SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
+        if (priorityByUid != null) {
+            priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT);
+        }
+        return priority;
+    }
+
+    @Override
+    public void setPackagePriority(String packageName, int uid, int priority) {
+        if (priority == getPackagePriority(packageName, uid)) {
+            return;
+        }
+        SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
+        if (priorityByUid == null) {
+            priorityByUid = new SparseIntArray();
+            mPackagePriorities.put(packageName, priorityByUid);
+        }
+        priorityByUid.put(uid, priority);
+        updateConfig();
+    }
+
+    public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
+        if (filter == null) {
+            final int N = mSignalExtractors.length;
+            pw.print(prefix);
+            pw.print("mSignalExtractors.length = ");
+            pw.println(N);
+            for (int i = 0; i < N; i++) {
+                pw.print(prefix);
+                pw.print("  ");
+                pw.println(mSignalExtractors[i]);
+            }
+        }
+        final int N = mPackagePriorities.size();
+        if (filter == null) {
+            pw.print(prefix);
+            pw.println("package priorities:");
+        }
+        for (int i = 0; i < N; i++) {
+            String name = mPackagePriorities.keyAt(i);
+            if (filter == null || filter.matches(name)) {
+                SparseIntArray priorityByUid = mPackagePriorities.get(name);
+                final int M = priorityByUid.size();
+                for (int j = 0; j < M; j++) {
+                    int uid = priorityByUid.keyAt(j);
+                    int priority = priorityByUid.get(uid);
+                    pw.print(prefix);
+                    pw.print("  ");
+                    pw.print(name);
+                    pw.print(" (");
+                    pw.print(uid);
+                    pw.print(") has priority: ");
+                    pw.println(priority);
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 4ac2dcc..bdc364c 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -264,6 +264,11 @@
         return validatePeople(record);
     }
 
+    @Override
+    public void setConfig(RankingConfig config) {
+        // ignore: config has no relevant information yet.
+    }
+
     private static class LookupResult {
         private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
         public static final int INVALID_ID = -1;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 1d53016..355f34f 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -75,7 +75,7 @@
                         schedule(BackgroundDexOptService.this);
                         return;
                     }
-                    pm.performDexOpt(pkg, false);
+                    pm.performDexOpt(pkg, null /* instruction set */, false);
                 }
                 // ran to completion, so we abandon our timeslice and do not reschedule
                 jobFinished(jobParams, false);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 1193968..8b0a46d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -309,6 +309,15 @@
         return execute(builder.toString());
     }
 
+    public int deleteCodeCacheFiles(String name, int userId) {
+        StringBuilder builder = new StringBuilder("rmcodecache");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
     public int createUserData(String name, int uid, int userId, String seinfo) {
         StringBuilder builder = new StringBuilder("mkuserdata");
         builder.append(' ');
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 190e87c..db915e2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -76,6 +76,10 @@
     @GuardedBy("mSessions")
     private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
 
+    /** Historical sessions kept around for debugging purposes */
+    @GuardedBy("mSessions")
+    private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>();
+
     private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>();
 
     private static final FilenameFilter sStageFilter = new FilenameFilter() {
@@ -344,18 +348,29 @@
     }
 
     void dump(IndentingPrintWriter pw) {
-        pw.println("Active install sessions:");
-        pw.increaseIndent();
         synchronized (mSessions) {
-            final int N = mSessions.size();
+            pw.println("Active install sessions:");
+            pw.increaseIndent();
+            int N = mSessions.size();
             for (int i = 0; i < N; i++) {
                 final PackageInstallerSession session = mSessions.valueAt(i);
                 session.dump(pw);
                 pw.println();
             }
+            pw.println();
+            pw.decreaseIndent();
+
+            pw.println("Historical install sessions:");
+            pw.increaseIndent();
+            N = mHistoricalSessions.size();
+            for (int i = 0; i < N; i++) {
+                final PackageInstallerSession session = mHistoricalSessions.valueAt(i);
+                session.dump(pw);
+                pw.println();
+            }
+            pw.println();
+            pw.decreaseIndent();
         }
-        pw.println();
-        pw.decreaseIndent();
     }
 
     class Callback {
@@ -367,6 +382,7 @@
             notifySessionFinished(session.sessionId, success);
             synchronized (mSessions) {
                 mSessions.remove(session.sessionId);
+                mHistoricalSessions.put(session.sessionId, session);
             }
             writeSessionsAsync();
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 31d9704..0e6a3f0 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -65,6 +65,7 @@
 
 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     private static final String TAG = "PackageInstaller";
+    private static final boolean LOGD = true;
 
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -435,35 +436,25 @@
      */
     private void spliceExistingFilesIntoStage() throws PackageManagerException {
         final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
-        final File existingDir = new File(app.getBaseCodePath());
 
-        try {
-            linkTreeIgnoringExisting(existingDir, sessionStageDir);
-        } catch (ErrnoException e) {
-            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                    "Failed to splice into stage");
-        }
-    }
+        int n = 0;
+        final File[] oldFiles = new File(app.getCodePath()).listFiles();
+        if (!ArrayUtils.isEmpty(oldFiles)) {
+            for (File oldFile : oldFiles) {
+                if (!PackageParser.isApkFile(oldFile)) continue;
 
-    /**
-     * Recursively hard link all files from source directory tree to target.
-     * When a file already exists in the target tree, it leaves that file
-     * intact.
-     */
-    private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
-        final File[] sourceContents = sourceDir.listFiles();
-        if (ArrayUtils.isEmpty(sourceContents)) return;
-
-        for (File sourceFile : sourceContents) {
-            final File targetFile = new File(targetDir, sourceFile.getName());
-
-            if (sourceFile.isDirectory()) {
-                targetFile.mkdir();
-                linkTreeIgnoringExisting(sourceFile, targetFile);
-            } else {
-                Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
+                final File newFile = new File(sessionStageDir, oldFile.getName());
+                try {
+                    Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath());
+                    n++;
+                } catch (ErrnoException e) {
+                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                            "Failed to splice into stage", e);
+                }
             }
         }
+
+        if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage");
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index cfba19c..727cff0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -26,7 +26,6 @@
 import static android.content.pm.PackageManager.INSTALL_EXTERNAL;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
-import static android.content.pm.PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -165,6 +164,7 @@
 import android.util.AtomicFile;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
+import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.LogPrinter;
 import android.util.PrintStreamPrinter;
@@ -1713,7 +1713,7 @@
                 // NOTE: We ignore potential failures here during a system scan (like
                 // the rest of the commands above) because there's precious little we
                 // can do about it. A settings error is reported, though.
-                adjustCpuAbisForSharedUserLPw(setting.packages, null,
+                adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
                         false /* force dexopt */, false /* defer dexopt */);
             }
 
@@ -1751,6 +1751,16 @@
                 mSettings.readDefaultPreferredAppsLPw(this, 0);
             }
 
+            // If this is first boot after an OTA, and a normal boot, then
+            // we need to clear code cache directories.
+            if (!Build.FINGERPRINT.equals(mSettings.mFingerprint) && !onlyCore) {
+                Slog.i(TAG, "Build fingerprint changed; clearing code caches");
+                for (String pkgName : mSettings.mPackages.keySet()) {
+                    deleteCodeCacheDirsLI(pkgName);
+                }
+                mSettings.mFingerprint = Build.FINGERPRINT;
+            }
+
             // All the changes are done during package scanning.
             mSettings.updateInternalDatabaseVersion();
 
@@ -4531,24 +4541,29 @@
                 }
                 PackageParser.Package p = pkg;
                 synchronized (mInstallLock) {
-                    if (p.mDexOptNeeded) {
-                        performDexOptLI(p, false /* force dex */, false /* defer */,
-                                true /* include dependencies */);
-                    }
+                    performDexOptLI(p, null /* instruction sets */, false /* force dex */, false /* defer */,
+                            true /* include dependencies */);
                 }
             }
         }
     }
 
     @Override
-    public boolean performDexOpt(String packageName) {
-        enforceSystemOrRoot("Only the system can request dexopt be performed");
-        return performDexOpt(packageName, true);
+    public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
+        return performDexOpt(packageName, instructionSet, true);
     }
 
-    public boolean performDexOpt(String packageName, boolean updateUsage) {
+    private static String getPrimaryInstructionSet(ApplicationInfo info) {
+        if (info.primaryCpuAbi == null) {
+            return getPreferredInstructionSet();
+        }
 
+        return VMRuntime.getInstructionSet(info.primaryCpuAbi);
+    }
+
+    public boolean performDexOpt(String packageName, String instructionSet, boolean updateUsage) {
         PackageParser.Package p;
+        final String targetInstructionSet;
         synchronized (mPackages) {
             p = mPackages.get(packageName);
             if (p == null) {
@@ -4558,13 +4573,17 @@
                 p.mLastPackageUsageTimeInMills = System.currentTimeMillis();
             }
             mPackageUsage.write(false);
-            if (!p.mDexOptNeeded) {
+
+            targetInstructionSet = instructionSet != null ? instructionSet :
+                    getPrimaryInstructionSet(p.applicationInfo);
+            if (p.mDexOptPerformed.contains(targetInstructionSet)) {
                 return false;
             }
         }
 
         synchronized (mInstallLock) {
-            return performDexOptLI(p, false /* force dex */, false /* defer */,
+            final String[] instructionSets = new String[] { targetInstructionSet };
+            return performDexOptLI(p, instructionSets, false /* force dex */, false /* defer */,
                     true /* include dependencies */) == DEX_OPT_PERFORMED;
         }
     }
@@ -4574,9 +4593,9 @@
         synchronized (mPackages) {
             for (PackageParser.Package p : mPackages.values()) {
                 if (DEBUG_DEXOPT) {
-                    Log.i(TAG, p.packageName + " mDexOptNeeded=" + p.mDexOptNeeded);
+                    Log.i(TAG, p.packageName + " mDexOptPerformed=" + p.mDexOptPerformed.toArray());
                 }
-                if (!p.mDexOptNeeded) {
+                if (!p.mDexOptPerformed.isEmpty()) {
                     continue;
                 }
                 if (pkgs == null) {
@@ -4636,7 +4655,7 @@
             return DEX_OPT_SKIPPED;
         }
 
-        final Collection<String> paths = pkg.getAllCodePaths();
+        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
         boolean performedDexOpt = false;
         // There are three basic cases here:
         // 1.) we need to dexopt, either because we are forced or it is needed
@@ -4644,11 +4663,16 @@
         // 3.) we are skipping an unneeded dexopt
         for (String path : paths) {
             for (String instructionSet : instructionSets) {
+                if (!forceDex && pkg.mDexOptPerformed.contains(instructionSet)) {
+                    continue;
+                }
+
                 try {
                     final boolean isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
                             pkg.packageName, instructionSet, defer);
                     if (forceDex || (!defer && isDexOptNeeded)) {
-                        Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName + " isa=" + instructionSet);
+                        Log.i(TAG, "Running dexopt on: " + path + " pkg="
+                                + pkg.applicationInfo.packageName + " isa=" + instructionSet);
                         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
                         final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
                                 pkg.packageName, instructionSet);
@@ -4657,10 +4681,10 @@
                             // Don't bother running dexopt again if we failed, it will probably
                             // just result in an error again. Also, don't bother dexopting for other
                             // paths & ISAs.
-                            pkg.mDexOptNeeded = false;
                             return DEX_OPT_FAILED;
                         } else {
                             performedDexOpt = true;
+                            pkg.mDexOptPerformed.add(instructionSet);
                         }
                     }
 
@@ -4694,7 +4718,6 @@
         // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
         // we've skipped all of them because they are up to date. In both cases this
         // package doesn't need dexopt any longer.
-        pkg.mDexOptNeeded = false;
         return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
     }
 
@@ -4750,8 +4773,8 @@
         return allInstructionSets;
     }
 
-    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
-            boolean inclDependencies) {
+    private int performDexOptLI(PackageParser.Package pkg, String[] instructionSets,
+                                boolean forceDex, boolean defer, boolean inclDependencies) {
         HashSet<String> done;
         if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
             done = new HashSet<String>();
@@ -4759,7 +4782,7 @@
         } else {
             done = null;
         }
-        return performDexOptLI(pkg, null /* target instruction sets */,  forceDex, defer, done);
+        return performDexOptLI(pkg, instructionSets,  forceDex, defer, done);
     }
 
     private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
@@ -4826,6 +4849,18 @@
         return res;
     }
 
+    private int deleteCodeCacheDirsLI(String packageName) {
+        int[] users = sUserManager.getUserIds();
+        int res = 0;
+        for (int user : users) {
+            int resInner = mInstaller.deleteCodeCacheFiles(packageName, user);
+            if (resInner < 0) {
+                res = resInner;
+            }
+        }
+        return res;
+    }
+
     private void addSharedLibraryLPw(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
             PackageParser.Package changingLib) {
         if (file.path != null) {
@@ -5428,11 +5463,8 @@
                         }
                     }
 
-                    if (abi32 < 0 && abi32 != PackageManager.NO_NATIVE_LIBRARIES) {
-                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                                "Error unpackaging 32 bit native libs for multiarch app, errorCode="
-                                + abi32);
-                    }
+                    maybeThrowExceptionForMultiArchCopy(
+                            "Error unpackaging 32 bit native libs for multiarch app.", abi32);
 
                     if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                         if (isAsec) {
@@ -5443,11 +5475,8 @@
                         }
                     }
 
-                    if (abi64 < 0 && abi64 != PackageManager.NO_NATIVE_LIBRARIES) {
-                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                                "Error unpackaging 64 bit native libs for multiarch app, errorCode="
-                                + abi32);
-                    }
+                    maybeThrowExceptionForMultiArchCopy(
+                            "Error unpackaging 64 bit native libs for multiarch app.", abi64);
 
                     if (abi64 >= 0) {
                         pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
@@ -5489,6 +5518,8 @@
 
                     if (copyRet >= 0) {
                         pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
+                    } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && abiOverride != null) {
+                        pkg.applicationInfo.primaryCpuAbi = abiOverride;
                     }
                 }
             } catch (IOException ioe) {
@@ -5544,15 +5575,12 @@
             // We also do this *before* we perform dexopt on this package, so that
             // we can avoid redundant dexopts, and also to make sure we've got the
             // code and package path correct.
-            if (!adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
-                    pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0)) {
-                throw new PackageManagerException(INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
-                        "scanPackageLI");
-            }
+            adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
+                    pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0);
         }
 
         if ((scanMode&SCAN_NO_DEX) == 0) {
-            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+            if (performDexOptLI(pkg, null /* instruction sets */, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                     == DEX_OPT_FAILED) {
                 if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                     removeDataDirsLI(pkg.packageName);
@@ -5631,7 +5659,8 @@
             if ((scanMode&SCAN_NO_DEX) == 0) {
                 for (int i=0; i<clientLibPkgs.size(); i++) {
                     PackageParser.Package clientPkg = clientLibPkgs.get(i);
-                    if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+                    if (performDexOptLI(clientPkg, null /* instruction sets */,
+                            forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                             == DEX_OPT_FAILED) {
                         if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                             removeDataDirsLI(pkg.packageName);
@@ -6047,7 +6076,7 @@
      * NOTE: We currently only match for the primary CPU abi string. Matching the secondary
      * adds unnecessary complexity.
      */
-    private boolean adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
+    private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
             PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt) {
         String requiredInstructionSet = null;
         if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) {
@@ -6061,27 +6090,23 @@
             // when scannedPackage is an update of an existing package. Without this check,
             // we will never be able to change the ABI of any package belonging to a shared
             // user, even if it's compatible with other packages.
-            if (scannedPackage == null || ! scannedPackage.packageName.equals(ps.name)) {
+            if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
                 if (ps.primaryCpuAbiString == null) {
                     continue;
                 }
 
                 final String instructionSet = VMRuntime.getInstructionSet(ps.primaryCpuAbiString);
-                if (requiredInstructionSet != null) {
-                    if (!instructionSet.equals(requiredInstructionSet)) {
-                        // We have a mismatch between instruction sets (say arm vs arm64).
-                        // bail out.
-                        String errorMessage = "Instruction set mismatch, "
-                                + ((requirer == null) ? "[caller]" : requirer)
-                                + " requires " + requiredInstructionSet + " whereas " + ps
-                                + " requires " + instructionSet;
-                        Slog.e(TAG, errorMessage);
+                if (requiredInstructionSet != null && !instructionSet.equals(requiredInstructionSet)) {
+                    // We have a mismatch between instruction sets (say arm vs arm64) warn about
+                    // this but there's not much we can do.
+                    String errorMessage = "Instruction set mismatch, "
+                            + ((requirer == null) ? "[caller]" : requirer)
+                            + " requires " + requiredInstructionSet + " whereas " + ps
+                            + " requires " + instructionSet;
+                    Slog.w(TAG, errorMessage);
+                }
 
-                        reportSettingsProblem(Log.WARN, errorMessage);
-                        // Give up, don't bother making any other changes to the package settings.
-                        return false;
-                    }
-                } else {
+                if (requiredInstructionSet == null) {
                     requiredInstructionSet = instructionSet;
                     requirer = ps;
                 }
@@ -6115,10 +6140,11 @@
                         ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
                         Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi);
 
-                        if (performDexOptLI(ps.pkg, forceDexOpt, deferDexOpt, true) == DEX_OPT_FAILED) {
+                        if (performDexOptLI(ps.pkg, null /* instruction sets */, forceDexOpt,
+                                deferDexOpt, true) == DEX_OPT_FAILED) {
                             ps.primaryCpuAbiString = null;
                             ps.pkg.applicationInfo.primaryCpuAbi = null;
-                            return false;
+                            return;
                         } else {
                             mInstaller.rmdex(ps.codePathString, getPreferredInstructionSet());
                         }
@@ -6126,8 +6152,6 @@
                 }
             }
         }
-
-        return true;
     }
 
     private void setUpCustomResolverActivity(PackageParser.Package pkg) {
@@ -6194,13 +6218,18 @@
         final ApplicationInfo info = pkg.applicationInfo;
         final String codePath = pkg.codePath;
         final File codeFile = new File(codePath);
+        // If "/system/lib64/apkname" exists, assume that is the per-package
+        // native library directory to use; otherwise use "/system/lib/apkname".
+        final String apkRoot = calculateApkRoot(info.sourceDir);
 
         final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info);
         final boolean asecApp = isForwardLocked(info) || isExternal(info);
 
+
         info.nativeLibraryRootDir = null;
         info.nativeLibraryRootRequiresIsa = false;
         info.nativeLibraryDir = null;
+        info.secondaryNativeLibraryDir = null;
 
         if (bundledApp) {
             // Monolithic bundled install
@@ -6214,38 +6243,43 @@
             // is just the default path.
             final String apkName = deriveCodePathName(codePath);
             final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME;
-            info.nativeLibraryRootDir = Environment.buildPath(new File(info.apkRoot), libDir,
+            info.nativeLibraryRootDir = Environment.buildPath(new File(apkRoot), libDir,
                     apkName).getAbsolutePath();
             info.nativeLibraryRootRequiresIsa = false;
 
+            info.nativeLibraryDir = info.nativeLibraryRootDir;
+            if (info.secondaryCpuAbi != null) {
+                final String secondaryLibDir = is64Bit ? LIB_DIR_NAME : LIB64_DIR_NAME;
+                info.secondaryNativeLibraryDir = Environment.buildPath(new File(apkRoot),
+                        secondaryLibDir, apkName).getAbsolutePath();
+            }
         } else if (isApkFile(codeFile)) {
             // Monolithic install
             if (asecApp) {
                 info.nativeLibraryRootDir = new File(codeFile.getParentFile(), LIB_DIR_NAME)
                         .getAbsolutePath();
-                info.nativeLibraryRootRequiresIsa = false;
             } else {
                 final String apkName = deriveCodePathName(codePath);
                 info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)
                         .getAbsolutePath();
-                info.nativeLibraryRootRequiresIsa = false;
             }
+
+            info.nativeLibraryRootRequiresIsa = false;
+            info.nativeLibraryDir = info.nativeLibraryRootDir;
         } else {
             // Cluster install
             info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();
             info.nativeLibraryRootRequiresIsa = true;
-        }
 
-        if (info.nativeLibraryRootRequiresIsa) {
             if (info.primaryCpuAbi != null) {
                 info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
                         VMRuntime.getInstructionSet(info.primaryCpuAbi)).getAbsolutePath();
-            } else {
-                Slog.w(TAG, "Package " + info.packageName
-                        + " missing ABI; unable to derive nativeLibraryDir");
             }
-        } else {
-            info.nativeLibraryDir = info.nativeLibraryRootDir;
+
+            if (info.secondaryCpuAbi != null) {
+                info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir,
+                        VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath();
+            }
         }
     }
 
@@ -6263,7 +6297,6 @@
         // If "/system/lib64/apkname" exists, assume that is the per-package
         // native library directory to use; otherwise use "/system/lib/apkname".
         final String apkRoot = calculateApkRoot(pkg.applicationInfo.sourceDir);
-        pkg.applicationInfo.apkRoot = apkRoot;
         setBundledAppAbi(pkg, apkRoot, apkName);
         // pkgSetting might be null during rescan following uninstall of updates
         // to a bundled app, so accommodate that possibility.  The settings in
@@ -9264,29 +9297,13 @@
                     if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                         copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot,
                                 Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */);
-                        if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
-                            Slog.w(TAG, "Failure copying 32 bit native libraries [errorCode=" + copyRet + "]");
-                            return copyRet;
-                        }
-                    }
-
-                    if (DEBUG_ABI_SELECTION && copyRet >= 0) {
-                        Log.d(TAG, "Installed 32 bit libraries under: " + codeFile + " abi=" +
-                                Build.SUPPORTED_32_BIT_ABIS[copyRet]);
+                        maybeThrowExceptionForMultiArchCopy("Failure copying 32 bit native libraries", copyRet);
                     }
 
                     if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                         copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot,
                                 Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */);
-                        if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
-                            Slog.w(TAG, "Failure copying 64 bit native libraries [errorCode=" + copyRet + "]");
-                            return copyRet;
-                        }
-                    }
-
-                    if (DEBUG_ABI_SELECTION && copyRet >= 0) {
-                        Log.d(TAG, "Installed 64 bit libraries under: " + codeFile + " abi=" +
-                                Build.SUPPORTED_64_BIT_ABIS[copyRet]);
+                        maybeThrowExceptionForMultiArchCopy("Failure copying 64 bit native libraries", copyRet);
                     }
                 } else {
                     String[] abiList = (abiOverride != null) ?
@@ -9303,14 +9320,13 @@
                         Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]");
                         return copyRet;
                     }
-
-                    if (DEBUG_ABI_SELECTION && copyRet >= 0) {
-                        Log.d(TAG, "Installed libraries under: " + codeFile + " abi=" + abiList[copyRet]);
-                    }
                 }
             } catch (IOException e) {
                 Slog.e(TAG, "Copying native libraries failed", e);
                 ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+            } catch (PackageManagerException pme) {
+                Slog.e(TAG, "Copying native libraries failed", pme);
+                ret = pme.error;
             } finally {
                 IoUtils.closeQuietly(handle);
             }
@@ -9459,6 +9475,16 @@
         return !asecPath.startsWith(mAsecInternalPath);
     }
 
+    private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws
+            PackageManagerException {
+        if (copyRet < 0) {
+            if (copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
+                    copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
+                throw new PackageManagerException(copyRet, message);
+            }
+        }
+    }
+
     /**
      * Extract the MountService "container ID" from the full code path of an
      * .apk.
@@ -9879,6 +9905,18 @@
             Slog.w(TAG, msg);
         }
 
+        public void setError(String msg, PackageParserException e) {
+            returnCode = e.error;
+            returnMsg = ExceptionUtils.getCompleteMessage(msg, e);
+            Slog.w(TAG, msg, e);
+        }
+
+        public void setError(String msg, PackageManagerException e) {
+            returnCode = e.error;
+            returnMsg = ExceptionUtils.getCompleteMessage(msg, e);
+            Slog.w(TAG, msg, e);
+        }
+
         // In some error cases we want to convey more info back to the observer
         String origPackage;
         String origPermission;
@@ -9932,8 +9970,7 @@
             }
 
         } catch (PackageManagerException e) {
-            res.setError(e.error,
-                    "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
+            res.setError("Package couldn't be installed in " + pkg.codePath, e);
         }
     }
 
@@ -9988,6 +10025,7 @@
                 perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
             }
         }
+
         boolean sysPkg = (isSystemApp(oldPackage));
         if (sysPkg) {
             replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
@@ -10025,14 +10063,14 @@
             deletedPkg = false;
         } else {
             // Successfully deleted the old package. Now proceed with re-installation
+            deleteCodeCacheDirsLI(pkgName);
             try {
                 final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
                         scanMode | SCAN_UPDATE_TIME, System.currentTimeMillis(), user, abiOverride);
                 updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
                 updatedSettings = true;
             } catch (PackageManagerException e) {
-                res.setError(e.error,
-                        "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
+                res.setError("Package couldn't be installed in " + pkg.codePath, e);
             }
         }
 
@@ -10135,8 +10173,10 @@
                 res.removedInfo.args = null;
             }
         }
-        
+
         // Successfully disabled the old package. Now proceed with re-installation
+        deleteCodeCacheDirsLI(packageName);
+
         res.returnCode = PackageManager.INSTALL_SUCCEEDED;
         pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
 
@@ -10163,8 +10203,7 @@
             }
 
         } catch (PackageManagerException e) {
-            res.setError(e.error,
-                    "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
+            res.setError("Package couldn't be installed in " + pkg.codePath, e);
         }
 
         if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -10209,7 +10248,7 @@
                  * remove the target to make sure there isn't a stale
                  * file from a previous version of the package.
                  */
-                    newPackage.mDexOptNeeded = true;
+                    newPackage.mDexOptPerformed.clear();
                     mInstaller.rmdex(oldCodePath, instructionSet);
                     mInstaller.rmdex(newPackage.baseCodePath, instructionSet);
                 }
@@ -10302,7 +10341,7 @@
         try {
             pkg = pp.parsePackage(tmpPackageFile, parseFlags);
         } catch (PackageParserException e) {
-            res.setError(e.error, "Failed parse during installPackageLI: " + e.getMessage());
+            res.setError("Failed parse during installPackageLI", e);
             return;
         }
 
@@ -10318,7 +10357,7 @@
             pp.collectCertificates(pkg, parseFlags);
             pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
-            res.setError(e.error, "Failed collect during installPackageLI: " + e.getMessage());
+            res.setError("Failed collect during installPackageLI", e);
             return;
         }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 40561ba..a3fd1df 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -30,6 +30,7 @@
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.PatternMatcher;
@@ -184,6 +185,12 @@
     int mInternalDatabaseVersion;
     int mExternalDatabaseVersion;
 
+    /**
+     * Last known value of {@link Build#FINGERPRINT}. Used to determine when an
+     * system update has occurred, meaning we need to clear code caches.
+     */
+    String mFingerprint;
+
     Boolean mReadExternalStorageEnforced;
 
     /** Device identity for the purpose of package verification. */
@@ -1664,6 +1671,7 @@
             serializer.startTag(null, "last-platform-version");
             serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform));
             serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform));
+            serializer.attribute(null, "fingerprint", mFingerprint);
             serializer.endTag(null, "last-platform-version");
 
             serializer.startTag(null, "database-version");
@@ -2089,6 +2097,7 @@
                     PackageManagerService.reportSettingsProblem(Log.INFO,
                             "No settings file; creating initial state");
                     mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion;
+                    mFingerprint = Build.FINGERPRINT;
                     return false;
                 }
                 str = new FileInputStream(mSettingsFilename);
@@ -2181,6 +2190,7 @@
                         }
                     } catch (NumberFormatException e) {
                     }
+                    mFingerprint = parser.getAttributeValue(null, "fingerprint");
                 } else if (tagName.equals("database-version")) {
                     mInternalDatabaseVersion = mExternalDatabaseVersion = 0;
                     try {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index e007600..0e1340c 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -46,6 +46,8 @@
 import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindBackgroundAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
@@ -72,44 +74,42 @@
             WindowManagerService.DEBUG_APP_TRANSITIONS;
     private static final boolean DEBUG_ANIM = WindowManagerService.DEBUG_ANIM;
 
-    /** Bit mask that is set for all enter transition. */
-    public static final int TRANSIT_ENTER_MASK = 0x1000;
-
-    /** Bit mask that is set for all exit transitions. */
-    public static final int TRANSIT_EXIT_MASK = 0x2000;
 
     /** Not set up for a transition. */
     public static final int TRANSIT_UNSET = -1;
     /** No animation for transition. */
     public static final int TRANSIT_NONE = 0;
     /** A window in a new activity is being opened on top of an existing one in the same task. */
-    public static final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK;
+    public static final int TRANSIT_ACTIVITY_OPEN = 6;
     /** The window in the top-most activity is being closed to reveal the
      * previous activity in the same task. */
-    public static final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK;
+    public static final int TRANSIT_ACTIVITY_CLOSE = 7;
     /** A window in a new task is being opened on top of an existing one
      * in another activity's task. */
-    public static final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK;
+    public static final int TRANSIT_TASK_OPEN = 8;
     /** A window in the top-most activity is being closed to reveal the
      * previous activity in a different task. */
-    public static final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK;
+    public static final int TRANSIT_TASK_CLOSE = 9;
     /** A window in an existing task is being displayed on top of an existing one
      * in another activity's task. */
-    public static final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK;
+    public static final int TRANSIT_TASK_TO_FRONT = 10;
     /** A window in an existing task is being put below all other tasks. */
-    public static final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK;
+    public static final int TRANSIT_TASK_TO_BACK = 11;
     /** A window in a new activity that doesn't have a wallpaper is being opened on top of one that
      * does, effectively closing the wallpaper. */
-    public static final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK;
+    public static final int TRANSIT_WALLPAPER_CLOSE = 12;
     /** A window in a new activity that does have a wallpaper is being opened on one that didn't,
      * effectively opening the wallpaper. */
-    public static final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK;
+    public static final int TRANSIT_WALLPAPER_OPEN = 13;
     /** A window in a new activity is being opened on top of an existing one, and both are on top
      * of the wallpaper. */
-    public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK;
+    public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14;
     /** The window in the top-most activity is being closed to reveal the previous activity, and
      * both are on top of the wallpaper. */
-    public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
+    public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15;
+    /** A window in a new task is being opened behind an existing one in another activity's task.
+     * The new window will show briefly and then be gone. */
+    public static final int TRANSIT_TASK_OPEN_BEHIND = 16;
 
     /** Fraction of animation at which the recents thumbnail becomes completely transparent */
     private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
@@ -811,6 +811,10 @@
                             ? WindowAnimation_wallpaperIntraCloseEnterAnimation
                             : WindowAnimation_wallpaperIntraCloseExitAnimation;
                     break;
+                case TRANSIT_TASK_OPEN_BEHIND:
+                    animAttr = enter
+                            ? WindowAnimation_launchTaskBehindSourceAnimation
+                            : WindowAnimation_launchTaskBehindBackgroundAnimation;
             }
             a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
@@ -896,9 +900,6 @@
             case TRANSIT_NONE: {
                 return "TRANSIT_NONE";
             }
-            case TRANSIT_EXIT_MASK: {
-                return "TRANSIT_EXIT_MASK";
-            }
             case TRANSIT_ACTIVITY_OPEN: {
                 return "TRANSIT_ACTIVITY_OPEN";
             }
@@ -929,6 +930,9 @@
             case TRANSIT_WALLPAPER_INTRA_CLOSE: {
                 return "TRANSIT_WALLPAPER_INTRA_CLOSE";
             }
+            case TRANSIT_TASK_OPEN_BEHIND: {
+                return "TRANSIT_TASK_OPEN_BEHIND";
+            }
             default: {
                 return "<UNKNOWN>";
             }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 63ae98e..874e105 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
+import android.graphics.Bitmap;
 import android.graphics.Matrix;
+import android.os.RemoteException;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.view.Display;
@@ -281,9 +283,21 @@
 
         final int N = mAllAppWinAnimators.size();
         for (int i=0; i<N; i++) {
-            mAllAppWinAnimators.get(i).finishExit();
+            final WindowStateAnimator winAnim = mAllAppWinAnimators.get(i);
+            if (mAppToken.mLaunchTaskBehind) {
+                winAnim.mWin.mExiting = true;
+            }
+            winAnim.finishExit();
         }
-        mAppToken.updateReportedVisibilityLocked();
+        if (mAppToken.mLaunchTaskBehind) {
+            try {
+                mService.mActivityManager.notifyLaunchTaskBehindComplete(mAppToken.token);
+            } catch (RemoteException e) {
+            }
+            mAppToken.mLaunchTaskBehind = false;
+        } else {
+            mAppToken.updateReportedVisibilityLocked();
+        }
 
         return false;
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 12c15e2..312689b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -109,6 +109,8 @@
 
     boolean mDeferRemoval;
 
+    boolean mLaunchTaskBehind;
+
     AppWindowToken(WindowManagerService _service, IApplicationToken _token,
             boolean _voiceInteraction) {
         super(_service, _token.asBinder(),
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index f1d0585d..27fff1d 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -248,7 +248,7 @@
         for (int i = windows.size() - 1; i >= 0; i--) {
             WindowState win = windows.get(i);
             WindowStateAnimator winAnimator = win.mWinAnimator;
-            final int flags = winAnimator.mAttrFlags;
+            final int flags = win.mAttrs.flags;
 
             if (winAnimator.mSurfaceControl != null) {
                 final boolean wasAnimating = winAnimator.mWasAnimating;
@@ -309,8 +309,7 @@
                             + " hidden=" + win.mRootToken.hidden
                             + " anim=" + win.mWinAnimator.mAnimation);
                 } else if (mPolicy.canBeForceHidden(win, win.mAttrs)) {
-                    final boolean hideWhenLocked =
-                            (winAnimator.mAttrFlags & FLAG_SHOW_WHEN_LOCKED) == 0;
+                    final boolean hideWhenLocked = (flags & FLAG_SHOW_WHEN_LOCKED) == 0;
                     final boolean changed;
                     if (((mForceHiding == KEYGUARD_ANIMATING_IN)
                                 && (!winAnimator.isAnimating() || hideWhenLocked))
@@ -416,7 +415,7 @@
                 continue;
             }
 
-            final int flags = winAnimator.mAttrFlags;
+            final int flags = win.mAttrs.flags;
 
             // If this window is animating, make a note that we have
             // an animating window and take care of a request to run
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 396ec8f..a5959d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -497,8 +497,8 @@
     boolean mStartingIconInTransition = false;
     boolean mSkipAppTransitionAnimation = false;
 
-    final ArrayList<AppWindowToken> mOpeningApps = new ArrayList<AppWindowToken>();
-    final ArrayList<AppWindowToken> mClosingApps = new ArrayList<AppWindowToken>();
+    final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<AppWindowToken>();
+    final ArraySet<AppWindowToken> mClosingApps = new ArraySet<AppWindowToken>();
 
     boolean mIsTouchDevice;
 
@@ -3234,6 +3234,12 @@
                                 SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN);
             }
 
+            if (atoken.mLaunchTaskBehind) {
+                // Differentiate the two animations. This one which is briefly on the screen
+                // gets the !enter animation, and the other activity which remains on the
+                // screen gets the enter animation. Both appear in the mOpeningApps set.
+                enter = false;
+            }
             Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
                     mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen,
                     isVoiceInteraction);
@@ -3449,14 +3455,14 @@
         EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
         Task task = new Task(atoken, stack, userId);
         mTaskIdToTask.put(taskId, task);
-        stack.addTask(task, true);
+        stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */);
         return task;
     }
 
     @Override
     public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
             int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
-            int configChanges, boolean voiceInteraction) {
+            int configChanges, boolean voiceInteraction, boolean launchTaskBehind) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "addAppToken()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3490,6 +3496,7 @@
             atoken.requestedOrientation = requestedOrientation;
             atoken.layoutConfigChanges = (configChanges &
                     (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
+            atoken.mLaunchTaskBehind = launchTaskBehind;
             if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken
                     + " to stack=" + stackId + " task=" + taskId + " at " + addPos);
 
@@ -3954,16 +3961,16 @@
         }
 
         synchronized(mWindowMap) {
-            if (DEBUG_APP_TRANSITIONS) {
-                RuntimeException e = new RuntimeException("here");
-                e.fillInStackTrace();
-                Slog.w(TAG, "Execute app transition: " + mAppTransition, e);
-            }
+            if (DEBUG_APP_TRANSITIONS) Slog.w(TAG, "Execute app transition: " + mAppTransition,
+                    new RuntimeException("here").fillInStackTrace());
             if (mAppTransition.isTransitionSet()) {
                 mAppTransition.setReady();
                 final long origId = Binder.clearCallingIdentity();
-                performLayoutAndPlaceSurfacesLocked();
-                Binder.restoreCallingIdentity(origId);
+                try {
+                    performLayoutAndPlaceSurfacesLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
             }
         }
     }
@@ -4370,17 +4377,11 @@
                 return;
             }
 
-            if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) {
-                RuntimeException e = null;
-                if (!HIDE_STACK_CRAWLS) {
-                    e = new RuntimeException();
-                    e.fillInStackTrace();
-                }
-                Slog.v(TAG, "setAppVisibility(" + token + ", visible=" + visible
-                        + "): " + mAppTransition
-                        + " hidden=" + wtoken.hidden
-                        + " hiddenRequested=" + wtoken.hiddenRequested, e);
-            }
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG, "setAppVisibility(" +
+                    token + ", visible=" + visible + "): " + mAppTransition +
+                    " hidden=" + wtoken.hidden + " hiddenRequested=" +
+                    wtoken.hiddenRequested, HIDE_STACK_CRAWLS ?
+                            null : new RuntimeException("here").fillInStackTrace());
 
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
@@ -4428,6 +4429,21 @@
                         wtoken.waitingToHide = true;
                     }
                 }
+                if (mAppTransition.getAppTransition() == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
+                    // We're launchingBehind, add the launching activity to mOpeningApps.
+                    final WindowState win =
+                            findFocusedWindowLocked(getDefaultDisplayContentLocked());
+                    if (win != null) {
+                        final AppWindowToken focusedToken = win.mAppToken;
+                        if (focusedToken != null) {
+                            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "TRANSIT_TASK_OPEN_BEHIND, " +
+                                    " adding " + focusedToken + " to mOpeningApps");
+                            // Force animation to be loaded.
+                            focusedToken.hidden = true;
+                            mOpeningApps.add(focusedToken);
+                        }
+                    }
+                }
                 return;
             }
 
@@ -8558,7 +8574,7 @@
             // all of the apps are ready.  Otherwise just go because
             // we'll unfreeze the display when everyone is ready.
             for (i=0; i<NN && goodToGo; i++) {
-                AppWindowToken wtoken = mOpeningApps.get(i);
+                AppWindowToken wtoken = mOpeningApps.valueAt(i);
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Check opening app=" + wtoken + ": allDrawn="
                         + wtoken.allDrawn + " startingDisplayed="
@@ -8631,12 +8647,12 @@
             for (i=0; i<NN; i++) {
                 final AppWindowToken wtoken;
                 if (i < NC) {
-                    wtoken = mClosingApps.get(i);
+                    wtoken = mClosingApps.valueAt(i);
                     if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
                         closingAppHasWallpaper = true;
                     }
                 } else {
-                    wtoken = mOpeningApps.get(i - NC);
+                    wtoken = mOpeningApps.valueAt(i - NC);
                     if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
                         openingAppHasWallpaper = true;
                     }
@@ -8710,7 +8726,7 @@
 
             NN = mOpeningApps.size();
             for (i=0; i<NN; i++) {
-                AppWindowToken wtoken = mOpeningApps.get(i);
+                AppWindowToken wtoken = mOpeningApps.valueAt(i);
                 final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
                 appAnimator.clearThumbnail();
@@ -8743,7 +8759,7 @@
             }
             NN = mClosingApps.size();
             for (i=0; i<NN; i++) {
-                AppWindowToken wtoken = mClosingApps.get(i);
+                AppWindowToken wtoken = mClosingApps.valueAt(i);
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
                 wtoken.mAppAnimator.clearThumbnail();
                 wtoken.inPendingTransaction = false;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index b24072f..49d4ae9 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -172,7 +172,6 @@
     /** Was this window last hidden? */
     boolean mLastHidden;
 
-    int mAttrFlags;
     int mAttrType;
 
     public WindowStateAnimator(final WindowState win) {
@@ -197,7 +196,6 @@
                 ? null : win.mAttachedWindow.mWinAnimator;
         mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator;
         mSession = win.mSession;
-        mAttrFlags = win.mAttrs.flags;
         mAttrType = win.mAttrs.type;
         mIsWallpaper = win.mIsWallpaper;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5445dc0..92d3d95 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3741,6 +3741,114 @@
     }
 
     @Override
+    public void enableSystemApp(ComponentName who, String packageName) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            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.");
+                }
+
+                UserInfo primaryUser = um.getProfileParent(userId);
+
+                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");
+            }
+
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            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.");
+                }
+
+                UserInfo primaryUser = um.getProfileParent(userId);
+
+                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) {
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
index 1db3c5e..4fe30e6 100644
--- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -23,17 +23,13 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.admin.IDevicePolicyManager;
-import android.content.AbstractRestrictionsProvider;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.IPermissionResponseCallback;
-import android.content.IRestrictionsProvider;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IRestrictionsManager;
 import android.content.RestrictionsManager;
-import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -127,58 +123,12 @@
                     enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" +
                             " match caller ");
                     // Prepare and broadcast the intent to the provider
-                    Intent intent = new Intent();
+                    Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_PERMISSION);
                     intent.setComponent(restrictionsProvider);
-                    new ProviderServiceConnection(intent, null, userHandle) {
-                        @Override
-                        public void run() throws RemoteException {
-                            if (DEBUG) {
-                                Log.i(LOG_TAG, "calling requestPermission for " + packageName
-                                        + ", type=" + requestType + ", data=" + requestData);
-                            }
-                            mRestrictionsProvider.requestPermission(packageName,
-                                    requestType, requestData);
-                        }
-                    }.bind();
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        }
-
-        @Override
-        public void getPermissionResponse(final String packageName, final String requestId,
-                final IPermissionResponseCallback callback) 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 fetch 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();
-                    intent.setComponent(restrictionsProvider);
-                    new ProviderServiceConnection(intent, callback.asBinder(), userHandle) {
-                        @Override
-                        public void run() throws RemoteException {
-                            if (DEBUG) {
-                                Log.i(LOG_TAG, "calling getPermissionResponse for " + packageName
-                                        + ", id=" + requestId);
-                            }
-                            Bundle response = mRestrictionsProvider.getPermissionResponse(
-                                    packageName, requestId);
-                            callback.onResponse(response);
-                        }
-                    }.bind();
+                    intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName);
+                    intent.putExtra(RestrictionsManager.EXTRA_REQUEST_TYPE, requestType);
+                    intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData);
+                    mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle));
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -226,81 +176,5 @@
                 // Shouldn't happen
             }
         }
-
-        abstract class ProviderServiceConnection
-                implements IBinder.DeathRecipient, ServiceConnection {
-
-            protected IRestrictionsProvider mRestrictionsProvider;
-            private Intent mIntent;
-            protected int mUserHandle;
-            protected IBinder mResponse;
-            private boolean mAbort;
-
-            public ProviderServiceConnection(Intent intent, IBinder response, int userHandle) {
-                mIntent = intent;
-                mResponse = response;
-                mUserHandle = userHandle;
-                if (mResponse != null) {
-                    try {
-                        mResponse.linkToDeath(this, 0 /* flags */);
-                    } catch (RemoteException re) {
-                        close();
-                    }
-                }
-            }
-
-            /** Bind to the RestrictionsProvider process */
-            public void bind() {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "binding to service: " + mIntent);
-                }
-                mContext.bindServiceAsUser(mIntent, this, Context.BIND_AUTO_CREATE,
-                        new UserHandle(mUserHandle));
-            }
-
-            private void close() {
-                mAbort = true;
-                unbind();
-            }
-
-            private void unbind() {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "unbinding from service");
-                }
-                mContext.unbindService(this);
-            }
-
-            /** Implement this to call the appropriate method on the service */
-            public abstract void run() throws RemoteException;
-
-            @Override
-            public void onServiceConnected(ComponentName name, IBinder service) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "connected to " + name);
-                }
-                mRestrictionsProvider = IRestrictionsProvider.Stub.asInterface(service);
-                if (!mAbort) {
-                    try {
-                        run();
-                    } catch (RemoteException re) {
-                        Log.w("RestrictionsProvider", "Remote exception: " + re);
-                    }
-                }
-                close();
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName name) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "disconnected from " + name);
-                }
-                mRestrictionsProvider = null;
-            }
-
-            @Override
-            public void binderDied() {
-                mAbort = true;
-            }
-        }
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index a1240f4..548e7d3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -23,8 +23,8 @@
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.hardware.soundtrigger.SoundTrigger;
-import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.Keyphrase;
+import android.hardware.soundtrigger.KeyphraseSoundModel;
 import android.util.Slog;
 
 import java.util.ArrayList;
@@ -32,6 +32,8 @@
 import java.util.UUID;
 
 /**
+ * Helper to manage the database of the sound models that have been registered on the device.
+ *
  * @hide
  */
 public class DatabaseHelper extends SQLiteOpenHelper {
@@ -86,14 +88,14 @@
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        // TODO(sansid): For now, drop older tables and recreate new ones.
+        // TODO: For now, drop older tables and recreate new ones.
         db.execSQL("DROP TABLE IF EXISTS " + KeyphraseContract.TABLE);
         db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE);
         onCreate(db);
     }
 
     /**
-     * TODO(sansid): Change to addOrUpdate to handle changes here.
+     * TODO: Change to addOrUpdate to handle changes here.
      */
     public void addKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
         SQLiteDatabase db = this.getWritableDatabase();
@@ -115,13 +117,13 @@
     /**
      * TODO(sansid): Change to addOrUpdate to handle changes here.
      */
-    private void addKeyphrase(UUID modelId, SoundTrigger.Keyphrase keyphrase) {
+    private void addKeyphrase(UUID modelId, Keyphrase keyphrase) {
         SQLiteDatabase db = this.getWritableDatabase();
         ContentValues values = new ContentValues();
         values.put(KeyphraseContract.KEY_ID, keyphrase.id);
-        values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes);
+        values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModeFlags);
         values.put(KeyphraseContract.KEY_SOUND_MODEL_ID, keyphrase.id);
-        values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.text);
+        values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.hintText);
         values.put(KeyphraseContract.KEY_LOCALE, keyphrase.locale);
         if (db.insert(KeyphraseContract.TABLE, null, values) == -1) {
             Slog.w(TAG, "Failed to persist keyphrase to database");
@@ -171,7 +173,7 @@
                 String locale = c.getString(c.getColumnIndex(KeyphraseContract.KEY_LOCALE));
                 String hintText = c.getString(c.getColumnIndex(KeyphraseContract.KEY_HINT_TEXT));
 
-                keyphrases.add(new Keyphrase(id, modes, locale, hintText, users));
+                keyphrases.add(new Keyphrase(id, hintText, locale, modes, users));
             } while (c.moveToNext());
         }
         Keyphrase[] keyphraseArr = new Keyphrase[keyphrases.size()];
diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java
index ba7e253..9f38d0d 100644
--- a/telecomm/java/android/telecomm/Call.java
+++ b/telecomm/java/android/telecomm/Call.java
@@ -73,6 +73,7 @@
         private final String mDisconnectCauseMsg;
         private final long mConnectTimeMillis;
         private final GatewayInfo mGatewayInfo;
+        private final int mVideoState;
 
         /**
          * @return The handle (e.g., phone number) to which the {@code Call} is currently
@@ -152,6 +153,13 @@
             return mGatewayInfo;
         }
 
+        /**
+         * @return Returns the video state of the {@code Call}.
+         */
+        public int getVideoState() {
+            return mVideoState;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o instanceof Details) {
@@ -167,7 +175,8 @@
                         Objects.equals(mDisconnectCauseCode, d.mDisconnectCauseCode) &&
                         Objects.equals(mDisconnectCauseMsg, d.mDisconnectCauseMsg) &&
                         Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
-                        Objects.equals(mGatewayInfo, d.mGatewayInfo);
+                        Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
+                        Objects.equals(mVideoState, d.mVideoState);
             }
             return false;
         }
@@ -184,7 +193,8 @@
                     Objects.hashCode(mDisconnectCauseCode) +
                     Objects.hashCode(mDisconnectCauseMsg) +
                     Objects.hashCode(mConnectTimeMillis) +
-                    Objects.hashCode(mGatewayInfo);
+                    Objects.hashCode(mGatewayInfo) +
+                    Objects.hashCode(mVideoState);
         }
 
         /** {@hide} */
@@ -198,7 +208,8 @@
                 int disconnectCauseCode,
                 String disconnectCauseMsg,
                 long connectTimeMillis,
-                GatewayInfo gatewayInfo) {
+                GatewayInfo gatewayInfo,
+                int videoState) {
             mHandle = handle;
             mHandlePresentation = handlePresentation;
             mCallerDisplayName = callerDisplayName;
@@ -209,6 +220,7 @@
             mDisconnectCauseMsg = disconnectCauseMsg;
             mConnectTimeMillis = connectTimeMillis;
             mGatewayInfo = gatewayInfo;
+            mVideoState = videoState;
         }
     }
 
@@ -545,7 +557,8 @@
                 inCallCall.getDisconnectCauseCode(),
                 inCallCall.getDisconnectCauseMsg(),
                 inCallCall.getConnectTimeMillis(),
-                inCallCall.getGatewayInfo());
+                inCallCall.getGatewayInfo(),
+                inCallCall.getVideoState());
         boolean detailsChanged = !Objects.equals(mDetails, details);
         if (detailsChanged) {
             mDetails = details;
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index fb820f0..db834a4 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -44,9 +44,9 @@
         public void onDestroyed(Connection c) {}
         public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {}
         public void onParentConnectionChanged(Connection c, Connection parent) {}
-        public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {}
-        public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {}
-        public void onSetStatusHints(Connection c, StatusHints statusHints) {}
+        public void onCallVideoProviderChanged(Connection c, CallVideoProvider callVideoProvider) {}
+        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
     }
 
     public final class State {
@@ -321,7 +321,7 @@
     public final void setCallVideoProvider(CallVideoProvider callVideoProvider) {
         mCallVideoProvider = callVideoProvider;
         for (Listener l : mListeners) {
-            l.onSetCallVideoProvider(this, callVideoProvider);
+            l.onCallVideoProviderChanged(this, callVideoProvider);
         }
     }
 
@@ -415,7 +415,7 @@
     public final void setAudioModeIsVoip(boolean isVoip) {
         mAudioModeIsVoip = isVoip;
         for (Listener l : mListeners) {
-            l.onSetAudioModeIsVoip(this, isVoip);
+            l.onAudioModeIsVoipChanged(this, isVoip);
         }
     }
 
@@ -427,7 +427,7 @@
     public final void setStatusHints(StatusHints statusHints) {
         mStatusHints = statusHints;
         for (Listener l : mListeners) {
-            l.onSetStatusHints(this, statusHints);
+            l.onStatusHintsChanged(this, statusHints);
         }
     }
 
@@ -516,7 +516,6 @@
      */
     protected void onPhoneAccountClicked() {}
 
-
     private void addChild(Connection connection) {
         Log.d(this, "adding child %s", connection);
         mChildConnections.add(connection);
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 178cee8..2a6804b 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -364,19 +364,19 @@
         }
 
         @Override
-        public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {
+        public void onCallVideoProviderChanged(Connection c, CallVideoProvider callVideoProvider) {
             String id = mIdByConnection.get(c);
             mAdapter.setCallVideoProvider(id, callVideoProvider);
         }
 
         @Override
-        public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {
+        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
             String id = mIdByConnection.get(c);
             mAdapter.setAudioModeIsVoip(id, isVoip);
         }
 
         @Override
-        public void onSetStatusHints(Connection c, StatusHints statusHints) {
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
             String id = mIdByConnection.get(c);
             mAdapter.setStatusHints(id, statusHints);
         }
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 355c260..db8395c 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -50,6 +50,7 @@
     private final String mParentCallId;
     private final List<String> mChildCallIds;
     private final StatusHints mStatusHints;
+    private final int mVideoState;
 
     /** @hide */
     public InCallCall(
@@ -69,7 +70,8 @@
             ICallVideoProvider callVideoProvider,
             String parentCallId,
             List<String> childCallIds,
-            StatusHints statusHints) {
+            StatusHints statusHints,
+            int videoState) {
         mId = id;
         mState = state;
         mDisconnectCauseCode = disconnectCauseCode;
@@ -87,6 +89,7 @@
         mParentCallId = parentCallId;
         mChildCallIds = childCallIds;
         mStatusHints = statusHints;
+        mVideoState = videoState;
     }
 
     /** The unique ID of the call. */
@@ -204,6 +207,14 @@
         return mStatusHints;
     }
 
+    /**
+     * The video state.
+     * @return The video state of the call.
+     */
+    public int getVideoState() {
+        return mVideoState;
+    }
+
     /** Responsible for creating InCallCall objects for deserialized Parcels. */
     public static final Parcelable.Creator<InCallCall> CREATOR =
             new Parcelable.Creator<InCallCall> () {
@@ -230,10 +241,12 @@
             List<String> childCallIds = new ArrayList<>();
             source.readList(childCallIds, classLoader);
             StatusHints statusHints = source.readParcelable(classLoader);
+            int videoState = source.readInt();
             return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg,
                     cannedSmsResponses, capabilities, connectTimeMillis, handle, handlePresentation,
                     callerDisplayName, callerDisplayNamePresentation, gatewayInfo,
-                    account, callVideoProvider, parentCallId, childCallIds, statusHints);
+                    account, callVideoProvider, parentCallId, childCallIds, statusHints,
+                    videoState);
         }
 
         @Override
@@ -269,6 +282,7 @@
         destination.writeString(mParentCallId);
         destination.writeList(mChildCallIds);
         destination.writeParcelable(mStatusHints, 0);
+        destination.writeInt(mVideoState);
     }
 
     @Override
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index a94841f..b50c1d7 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -121,4 +121,83 @@
      * wait for user confirmation before proceeding.
      */
     public static final char DTMF_CHARACTER_WAIT = ';';
+
+    /**
+     * TTY (teletypewriter) mode is off.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_OFF = 0;
+
+    /**
+     * TTY (teletypewriter) mode is on. The speaker is off and the microphone is muted. The user
+     * will communicate with the remote party by sending and receiving text messages.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_FULL = 1;
+
+    /**
+     * TTY (teletypewriter) mode is in hearing carryover mode (HCO). The microphone is muted but the
+     * speaker is on. The user will communicate with the remote party by sending text messages and
+     * hearing an audible reply.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_HCO = 2;
+
+    /**
+     * TTY (teletypewriter) mode is in voice carryover mode (VCO). The speaker is off but the
+     * microphone is still on. User will communicate with the remote party by speaking and receiving
+     * text message replies.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_VCO = 3;
+
+    /**
+     * Broadcast intent action indicating that the current TTY mode has changed. An intent extra
+     * provides this state as an int.
+     * @see #EXTRA_CURRENT_TTY_MODE
+     *
+     * @hide
+     */
+    public static final String ACTION_CURRENT_TTY_MODE_CHANGED =
+            "android.telecomm.intent.action.CURRENT_TTY_MODE_CHANGED";
+
+    /**
+     * The lookup key for an int that indicates the current TTY mode.
+     * Valid modes are:
+     * - {@link #TTY_MODE_OFF}
+     * - {@link #TTY_MODE_FULL}
+     * - {@link #TTY_MODE_HCO}
+     * - {@link #TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public static final String EXTRA_CURRENT_TTY_MODE =
+            "android.telecomm.intent.extra.CURRENT_TTY_MODE";
+
+    /**
+     * Broadcast intent action indicating that the TTY preferred operating mode
+     * has changed. An intent extra provides the new mode as an int.
+     * @see #EXTRA_TTY_PREFERRED_MODE
+     *
+     * @hide
+     */
+    public static final String ACTION_TTY_PREFERRED_MODE_CHANGED =
+            "android.telecomm.intent.action.TTY_PREFERRED_MODE_CHANGED";
+
+    /**
+     * The lookup key for an int that indicates preferred TTY mode.
+     * Valid modes are:
+     * - {@link #TTY_MODE_OFF}
+     * - {@link #TTY_MODE_FULL}
+     * - {@link #TTY_MODE_HCO}
+     * - {@link #TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public static final String EXTRA_TTY_PREFERRED_MODE =
+            "android.telecomm.intent.extra.TTY_PREFERRED";
 }
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index fcd2eba..8bf80bb 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -28,21 +28,9 @@
 /**
  * Provides access to Telecomm-related functionality.
  * TODO(santoscordon): Move this all into PhoneManager.
- * @hide
  */
 public class TelecommManager {
 
-    /**
-     * The extra used with an {@link android.content.Intent#ACTION_CALL} or
-     * {@link android.content.Intent#ACTION_DIAL} {@code Intent} to specify a {@link PhoneAccount}
-     * to use when making the call.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getParcelableExtra(String)}.
-     */
-    public static final String EXTRA_PHONE_ACCOUNT = "account";
-
     private static final String TAG = "TelecommManager";
     private static final String TELECOMM_SERVICE_NAME = "telecomm";
 
@@ -138,8 +126,6 @@
      * Remove all Accounts for a given package from the system.
      *
      * @param packageName A package name that may have registered Accounts.
-     *
-     * @hide
      */
     @SystemApi
     public void clearAccounts(String packageName) {
@@ -254,6 +240,44 @@
         }
     }
 
+    /**
+     * Returns whether TTY is supported on this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isTtySupported() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecommService().isTtySupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current TTY mode of the device. For TTY to be on the user must enable it in
+     * settings and have a wired headset plugged in. Valid modes are:
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_OFF}
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_FULL}
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_HCO}
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public int getCurrentTtyMode() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecommService().getCurrentTtyMode();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
+        }
+        return TelecommConstants.TTY_MODE_OFF;
+    }
+
     private ITelecommService getTelecommService() {
         return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME));
     }
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index 3334385..43caa1e 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -101,4 +101,14 @@
      * @see PhoneManager#handlePinMmi
      */
     boolean handlePinMmi(String dialString);
+
+    /**
+     * @see TelecomManager#isTtySupported
+     */
+    boolean isTtySupported();
+
+    /**
+     * @see TelecomManager#getCurrentTtyMode
+     */
+    int getCurrentTtyMode();
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c1eb843..5e6cb14 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1711,11 +1711,10 @@
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param alphaTag alpha-tagging of the dailing nubmer
      * @param number The dialing number
-     *
-     * @hide
      */
     public void setLine1NumberForDisplay(String alphaTag, String number) {
         setLine1NumberForDisplay(getDefaultSubscription(), alphaTag, number);
@@ -1729,12 +1728,11 @@
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param subId the subscriber that the alphatag and dialing number belongs to.
      * @param alphaTag alpha-tagging of the dailing nubmer
      * @param number The dialing number
-     *
-     * @hide
      */
     public void setLine1NumberForDisplay(long subId, String alphaTag, String number) {
         try {
@@ -2347,11 +2345,10 @@
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @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 {
@@ -2369,12 +2366,11 @@
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @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 {
@@ -2392,6 +2388,7 @@
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
@@ -2404,8 +2401,6 @@
      * @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) {
@@ -2423,6 +2418,7 @@
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param content String containing SAT/USAT response in hexadecimal
      *                format starting with command tag. See TS 102 223 for
@@ -2430,8 +2426,6 @@
      * @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 {
@@ -2443,11 +2437,16 @@
     }
 
     /**
-     * Read one of the NV items defined in {@link com.android.internal.telephony.RadioNVItems}.
+     * Read one of the NV items defined in com.android.internal.telephony.RadioNVItems.
      * Used for device configuration by some CDMA operators.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @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) {
@@ -2461,14 +2460,18 @@
         return "";
     }
 
-
     /**
-     * Write one of the NV items defined in {@link com.android.internal.telephony.RadioNVItems}.
+     * Write one of the NV items defined in com.android.internal.telephony.RadioNVItems.
      * Used for device configuration by some CDMA operators.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @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) {
@@ -2485,9 +2488,14 @@
     /**
      * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage.
      * Used for device configuration by some CDMA operators.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param preferredRoamingList byte array containing the new PRL.
      * @return true on success; false on any failure.
+     *
      * @hide
      */
     public boolean nvWriteCdmaPrl(byte[] preferredRoamingList) {
@@ -2505,9 +2513,14 @@
      * 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.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @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) {
@@ -2804,10 +2817,12 @@
     /**
      * Get the calculated preferred network type.
      * Used for debugging incorrect network type.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
      * @return the preferred network type, defined in RILConstants.java or -1 if
      *         none available.
-     * @hide
      */
     public int getCalculatedPreferredNetworkType() {
         try {
@@ -2823,9 +2838,12 @@
     /**
      * Get the preferred network type.
      * Used for device configuration by some CDMA operators.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @return the preferred network type, defined in RILConstants.java.
-     * @hide
      */
     public int getPreferredNetworkType() {
         try {
@@ -2841,10 +2859,13 @@
     /**
      * Set the preferred network type.
      * Used for device configuration by some CDMA operators.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @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 {
@@ -2860,10 +2881,13 @@
     /**
      * Set the CDMA subscription source.
      * Used for device supporting both NV and RUIM for CDMA.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param subscriptionType the subscription type, 0 for RUIM, 1 for NV.
      * @return true on success; false on any failure.
-     * @hide
      */
     public boolean setCdmaSubscription(int subscriptionType) {
         try {
@@ -3201,10 +3225,9 @@
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param enable true means enabling the simplified UI.
-     *
-     * @hide
      */
     public void enableSimplifiedNetworkSettings(boolean enable) {
         enableSimplifiedNetworkSettings(getDefaultSubscription(), enable);
@@ -3216,11 +3239,10 @@
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
      * @param subId for which the simplified UI should be enabled or disabled.
      * @param enable true means enabling the simplified UI.
-     *
-     * @hide
      */
     public void enableSimplifiedNetworkSettings(long subId, boolean enable) {
         try {
@@ -3237,8 +3259,6 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
      * @return true if the simplified UI is enabled.
-     *
-     * @hide
      */
     public boolean getSimplifiedNetworkSettingsEnabled() {
         return getSimplifiedNetworkSettingsEnabled(getDefaultSubscription());
@@ -3252,8 +3272,6 @@
      *
      * @param subId for which the simplified UI should be enabled or disabled.
      * @return true if the simplified UI is enabled.
-     *
-     * @hide
      */
     public boolean getSimplifiedNetworkSettingsEnabled(long subId) {
         try {
diff --git a/telephony/java/com/android/internal/telephony/IMms.aidl b/telephony/java/com/android/internal/telephony/IMms.aidl
index db93a4f..651205f 100644
--- a/telephony/java/com/android/internal/telephony/IMms.aidl
+++ b/telephony/java/com/android/internal/telephony/IMms.aidl
@@ -215,4 +215,29 @@
      */
     void sendStoredMessage(long subId, String callingPkg, in Uri messageUri,
             in PendingIntent sentIntent);
+
+    /**
+     * Turns on/off the flag to automatically write sent/received SMS/MMS messages into system
+     *
+     * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+     * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+     * automatically
+     *
+     * This flag can only be changed by default SMS apps
+     *
+     * @param callingPkg the name of the calling app package
+     * @param enabled Whether to enable message auto persisting
+     */
+    void setAutoPersisting(String callingPkg, boolean enabled);
+
+    /**
+     * Get the value of the flag to automatically write sent/received SMS/MMS messages into system
+     *
+     * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+     * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+     * automatically
+     *
+     * @return the current value of the auto persist flag
+     */
+    boolean getAutoPersisting();
 }
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 2ebce9b..8a2732d 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -195,6 +195,11 @@
     }
 
     @Override
+    public File getCodeCacheDir() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public File getExternalCacheDir() {
         throw new UnsupportedOperationException();
     }
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 9cbb455..b47cc3c 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -161,7 +161,7 @@
                 Log.e(TAG, "Error getting session", e);
                 return;
             }
-            mController = MediaController.fromToken(token);
+            mController = new MediaController(token);
             mContext.setMediaController(mController);
             mController.addCallback(mControllerCb, mHandler);
             mTransportControls = mController.getTransportControls();
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index 78353b2..feecfde 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.support.media.protocols.MediaPlayerProtocol;
 import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -84,7 +85,7 @@
                 .build());
         mRouter.setRoutingCallback(new RoutingCallback(), null);
 
-        mSession = man.createSession("OneMedia");
+        mSession = new MediaSession(mContext, "OneMedia");
         mSession.addCallback(mCallback);
         mSession.addTransportControlsCallback(new TransportCallback());
         mSession.setPlaybackState(mPlaybackState);
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
similarity index 98%
rename from tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml
rename to tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
index 30fb1b8..18d7755 100644
--- a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
@@ -19,7 +19,7 @@
         android:drawable="@drawable/vector_drawable12" />
     <item android:id="@+id/off"
         android:drawable="@drawable/vector_drawable12" />
-    <transition android:fromId="@+id/off" android:toId="@+id/on">
+    <transition android:fromId="@+id/off" android:toId="@+id/on" android:reversible="true">
         <animated-vector android:drawable="@drawable/vector_drawable12">
             <target
                 android:name="pie1"
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml
new file mode 100644
index 0000000..6a67b02
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.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-selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/on" android:state_checked="true"
+        android:drawable="@drawable/vector_drawable_grouping_1" />
+    <item android:id="@+id/off"
+        android:drawable="@drawable/vector_drawable_grouping_1" />
+    <transition android:fromId="@+id/off" android:toId="@+id/on"
+        android:drawable="@drawable/animation_vector_drawable_grouping_1"
+        android:reversible="true">
+    </transition>
+</animated-selector>
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml
new file mode 100644
index 0000000..65cf25b
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.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-selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/on" android:state_checked="true"
+        android:drawable="@drawable/vector_drawable_favorite" />
+    <item android:id="@+id/off"
+        android:drawable="@drawable/vector_drawable_favorite" />
+    <transition android:fromId="@+id/off" android:toId="@+id/on"
+        android:drawable="@drawable/animation_vector_drawable_favorite"
+        android:reversible="true">
+    </transition>
+</animated-selector>
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
index 0ae0136..566cc4b 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
@@ -27,7 +27,9 @@
     private static final String LOGCAT = "AnimatedStateVectorDrawableTest";
 
     protected int[] icon = {
-            R.drawable.state_animation_vector_drawable
+            R.drawable.state_animation_vector_drawable01,
+            R.drawable.state_animation_vector_drawable02,
+            R.drawable.state_animation_vector_drawable03,
     };
 
     @Override
@@ -37,7 +39,7 @@
         ScrollView scrollView = new ScrollView(this);
         GridLayout container = new GridLayout(this);
         scrollView.addView(container);
-        container.setColumnCount(1);
+        container.setColumnCount(5);
 
         for (int i = 0; i < icon.length; i++) {
             CheckBox button = new CheckBox(this);
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index a6c09f3..62c92a1 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -93,7 +93,7 @@
         }
         
         try {
-            mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false);
+            mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false, false);
             fail("IWindowManager.addAppToken did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 963c796..4f1d15e 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -931,6 +931,13 @@
 
     // Build an empty <application> tag (required).
     sp<XMLNode> app = XMLNode::newElement(filename, String16(), String16("application"));
+
+    // Add the 'hasCode' attribute which is never true for resource splits.
+    if (!addTagAttribute(app, RESOURCES_ANDROID_NAMESPACE, "hasCode",
+            "false", true, true)) {
+        return UNKNOWN_ERROR;
+    }
+
     manifest->addChild(app);
     root->addChild(manifest);
 
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 1942831..ead3b13 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -61,7 +61,7 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) rm -f $@
 	$(hide) ls -l $(built_framework_classes)
-	$(hide) java -jar $(built_layoutlib_create_jar) \
+	$(hide) java -ea -jar $(built_layoutlib_create_jar) \
 	             $@ \
 	             $(built_core_classes) \
 	             $(built_framework_classes) \
diff --git a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
new file mode 100644
index 0000000..a3ec2cc
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import java.awt.Composite;
+import java.awt.CompositeContext;
+import java.awt.RenderingHints;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+
+/*
+ * (non-Javadoc)
+ * The class is adapted from a demo tool for Blending Modes written by
+ * Romain Guy (romainguy@android.com). The tool is available at
+ * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
+ */
+public final class BlendComposite implements Composite {
+    public enum BlendingMode {
+        NORMAL,
+        AVERAGE,
+        MULTIPLY,
+        SCREEN,
+        DARKEN,
+        LIGHTEN,
+        OVERLAY,
+        HARD_LIGHT,
+        SOFT_LIGHT,
+        DIFFERENCE,
+        NEGATION,
+        EXCLUSION,
+        COLOR_DODGE,
+        INVERSE_COLOR_DODGE,
+        SOFT_DODGE,
+        COLOR_BURN,
+        INVERSE_COLOR_BURN,
+        SOFT_BURN,
+        REFLECT,
+        GLOW,
+        FREEZE,
+        HEAT,
+        ADD,
+        SUBTRACT,
+        STAMP,
+        RED,
+        GREEN,
+        BLUE,
+        HUE,
+        SATURATION,
+        COLOR,
+        LUMINOSITY
+    }
+
+    public static final BlendComposite Normal = new BlendComposite(BlendingMode.NORMAL);
+    public static final BlendComposite Average = new BlendComposite(BlendingMode.AVERAGE);
+    public static final BlendComposite Multiply = new BlendComposite(BlendingMode.MULTIPLY);
+    public static final BlendComposite Screen = new BlendComposite(BlendingMode.SCREEN);
+    public static final BlendComposite Darken = new BlendComposite(BlendingMode.DARKEN);
+    public static final BlendComposite Lighten = new BlendComposite(BlendingMode.LIGHTEN);
+    public static final BlendComposite Overlay = new BlendComposite(BlendingMode.OVERLAY);
+    public static final BlendComposite HardLight = new BlendComposite(BlendingMode.HARD_LIGHT);
+    public static final BlendComposite SoftLight = new BlendComposite(BlendingMode.SOFT_LIGHT);
+    public static final BlendComposite Difference = new BlendComposite(BlendingMode.DIFFERENCE);
+    public static final BlendComposite Negation = new BlendComposite(BlendingMode.NEGATION);
+    public static final BlendComposite Exclusion = new BlendComposite(BlendingMode.EXCLUSION);
+    public static final BlendComposite ColorDodge = new BlendComposite(BlendingMode.COLOR_DODGE);
+    public static final BlendComposite InverseColorDodge = new BlendComposite(BlendingMode.INVERSE_COLOR_DODGE);
+    public static final BlendComposite SoftDodge = new BlendComposite(BlendingMode.SOFT_DODGE);
+    public static final BlendComposite ColorBurn = new BlendComposite(BlendingMode.COLOR_BURN);
+    public static final BlendComposite InverseColorBurn = new BlendComposite(BlendingMode.INVERSE_COLOR_BURN);
+    public static final BlendComposite SoftBurn = new BlendComposite(BlendingMode.SOFT_BURN);
+    public static final BlendComposite Reflect = new BlendComposite(BlendingMode.REFLECT);
+    public static final BlendComposite Glow = new BlendComposite(BlendingMode.GLOW);
+    public static final BlendComposite Freeze = new BlendComposite(BlendingMode.FREEZE);
+    public static final BlendComposite Heat = new BlendComposite(BlendingMode.HEAT);
+    public static final BlendComposite Add = new BlendComposite(BlendingMode.ADD);
+    public static final BlendComposite Subtract = new BlendComposite(BlendingMode.SUBTRACT);
+    public static final BlendComposite Stamp = new BlendComposite(BlendingMode.STAMP);
+    public static final BlendComposite Red = new BlendComposite(BlendingMode.RED);
+    public static final BlendComposite Green = new BlendComposite(BlendingMode.GREEN);
+    public static final BlendComposite Blue = new BlendComposite(BlendingMode.BLUE);
+    public static final BlendComposite Hue = new BlendComposite(BlendingMode.HUE);
+    public static final BlendComposite Saturation = new BlendComposite(BlendingMode.SATURATION);
+    public static final BlendComposite Color = new BlendComposite(BlendingMode.COLOR);
+    public static final BlendComposite Luminosity = new BlendComposite(BlendingMode.LUMINOSITY);
+
+    private float alpha;
+    private BlendingMode mode;
+
+    private BlendComposite(BlendingMode mode) {
+        this(mode, 1.0f);
+    }
+
+    private BlendComposite(BlendingMode mode, float alpha) {
+        this.mode = mode;
+        setAlpha(alpha);
+    }
+
+    public static BlendComposite getInstance(BlendingMode mode) {
+        return new BlendComposite(mode);
+    }
+
+    public static BlendComposite getInstance(BlendingMode mode, float alpha) {
+        return new BlendComposite(mode, alpha);
+    }
+
+    public BlendComposite derive(BlendingMode mode) {
+        return this.mode == mode ? this : new BlendComposite(mode, getAlpha());
+    }
+
+    public BlendComposite derive(float alpha) {
+        return this.alpha == alpha ? this : new BlendComposite(getMode(), alpha);
+    }
+
+    public float getAlpha() {
+        return alpha;
+    }
+
+    public BlendingMode getMode() {
+        return mode;
+    }
+
+    private void setAlpha(float alpha) {
+        if (alpha < 0.0f || alpha > 1.0f) {
+            throw new IllegalArgumentException(
+                    "alpha must be comprised between 0.0f and 1.0f");
+        }
+
+        this.alpha = alpha;
+    }
+
+    @Override
+    public int hashCode() {
+        return Float.floatToIntBits(alpha) * 31 + mode.ordinal();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof BlendComposite)) {
+            return false;
+        }
+
+        BlendComposite bc = (BlendComposite) obj;
+
+        if (mode != bc.mode) {
+            return false;
+        }
+
+        return alpha == bc.alpha;
+    }
+
+    public CompositeContext createContext(ColorModel srcColorModel,
+                                          ColorModel dstColorModel,
+                                          RenderingHints hints) {
+        return new BlendingContext(this);
+    }
+
+    private static final class BlendingContext implements CompositeContext {
+        private final Blender blender;
+        private final BlendComposite composite;
+
+        private BlendingContext(BlendComposite composite) {
+            this.composite = composite;
+            this.blender = Blender.getBlenderFor(composite);
+        }
+
+        public void dispose() {
+        }
+
+        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
+            if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+                dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+                dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) {
+                throw new IllegalStateException(
+                        "Source and destination must store pixels as INT.");
+            }
+
+            int width = Math.min(src.getWidth(), dstIn.getWidth());
+            int height = Math.min(src.getHeight(), dstIn.getHeight());
+
+            float alpha = composite.getAlpha();
+
+            int[] srcPixel = new int[4];
+            int[] dstPixel = new int[4];
+            int[] result = new int[4];
+            int[] srcPixels = new int[width];
+            int[] dstPixels = new int[width];
+
+            for (int y = 0; y < height; y++) {
+                dstIn.getDataElements(0, y, width, 1, dstPixels);
+                if (alpha != 0) {
+                    src.getDataElements(0, y, width, 1, srcPixels);
+                    for (int x = 0; x < width; x++) {
+                        // pixels are stored as INT_ARGB
+                        // our arrays are [R, G, B, A]
+                        int pixel = srcPixels[x];
+                        srcPixel[0] = (pixel >> 16) & 0xFF;
+                        srcPixel[1] = (pixel >>  8) & 0xFF;
+                        srcPixel[2] = (pixel      ) & 0xFF;
+                        srcPixel[3] = (pixel >> 24) & 0xFF;
+
+                        pixel = dstPixels[x];
+                        dstPixel[0] = (pixel >> 16) & 0xFF;
+                        dstPixel[1] = (pixel >>  8) & 0xFF;
+                        dstPixel[2] = (pixel      ) & 0xFF;
+                        dstPixel[3] = (pixel >> 24) & 0xFF;
+
+                        result = blender.blend(srcPixel, dstPixel, result);
+
+                        // mixes the result with the opacity
+                        if (alpha == 1) {
+                            dstPixels[x] = (result[3] & 0xFF) << 24 |
+                                           (result[0] & 0xFF) << 16 |
+                                           (result[1] & 0xFF) <<  8 |
+                                           result[2] & 0xFF;
+                        } else {
+                            dstPixels[x] =
+                                    ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 |
+                                    ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 |
+                                    ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) <<  8 |
+                                    (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF;
+                        }
+
+                    }
+            }
+                dstOut.setDataElements(0, y, width, 1, dstPixels);
+            }
+        }
+    }
+
+    private static abstract class Blender {
+        public abstract int[] blend(int[] src, int[] dst, int[] result);
+
+        private static void RGBtoHSL(int r, int g, int b, float[] hsl) {
+            float var_R = (r / 255f);
+            float var_G = (g / 255f);
+            float var_B = (b / 255f);
+
+            float var_Min;
+            float var_Max;
+            float del_Max;
+
+            if (var_R > var_G) {
+                var_Min = var_G;
+                var_Max = var_R;
+            } else {
+                var_Min = var_R;
+                var_Max = var_G;
+            }
+            if (var_B > var_Max) {
+                var_Max = var_B;
+            }
+            if (var_B < var_Min) {
+                var_Min = var_B;
+            }
+
+            del_Max = var_Max - var_Min;
+
+            float H, S, L;
+            L = (var_Max + var_Min) / 2f;
+
+            if (del_Max - 0.01f <= 0.0f) {
+                H = 0;
+                S = 0;
+            } else {
+                if (L < 0.5f) {
+                    S = del_Max / (var_Max + var_Min);
+                } else {
+                    S = del_Max / (2 - var_Max - var_Min);
+                }
+
+                float del_R = (((var_Max - var_R) / 6f) + (del_Max / 2f)) / del_Max;
+                float del_G = (((var_Max - var_G) / 6f) + (del_Max / 2f)) / del_Max;
+                float del_B = (((var_Max - var_B) / 6f) + (del_Max / 2f)) / del_Max;
+
+                if (var_R == var_Max) {
+                    H = del_B - del_G;
+                } else if (var_G == var_Max) {
+                    H = (1 / 3f) + del_R - del_B;
+                } else {
+                    H = (2 / 3f) + del_G - del_R;
+                }
+                if (H < 0) {
+                    H += 1;
+                }
+                if (H > 1) {
+                    H -= 1;
+                }
+            }
+
+            hsl[0] = H;
+            hsl[1] = S;
+            hsl[2] = L;
+        }
+
+        private static void HSLtoRGB(float h, float s, float l, int[] rgb) {
+            int R, G, B;
+
+            if (s - 0.01f <= 0.0f) {
+                R = (int) (l * 255.0f);
+                G = (int) (l * 255.0f);
+                B = (int) (l * 255.0f);
+            } else {
+                float var_1, var_2;
+                if (l < 0.5f) {
+                    var_2 = l * (1 + s);
+                } else {
+                    var_2 = (l + s) - (s * l);
+                }
+                var_1 = 2 * l - var_2;
+
+                R = (int) (255.0f * hue2RGB(var_1, var_2, h + (1.0f / 3.0f)));
+                G = (int) (255.0f * hue2RGB(var_1, var_2, h));
+                B = (int) (255.0f * hue2RGB(var_1, var_2, h - (1.0f / 3.0f)));
+            }
+
+            rgb[0] = R;
+            rgb[1] = G;
+            rgb[2] = B;
+        }
+
+        private static float hue2RGB(float v1, float v2, float vH) {
+            if (vH < 0.0f) {
+                vH += 1.0f;
+            }
+            if (vH > 1.0f) {
+                vH -= 1.0f;
+            }
+            if ((6.0f * vH) < 1.0f) {
+                return (v1 + (v2 - v1) * 6.0f * vH);
+            }
+            if ((2.0f * vH) < 1.0f) {
+                return (v2);
+            }
+            if ((3.0f * vH) < 2.0f) {
+                return (v1 + (v2 - v1) * ((2.0f / 3.0f) - vH) * 6.0f);
+            }
+            return (v1);
+        }
+
+        public static Blender getBlenderFor(BlendComposite composite) {
+            switch (composite.getMode()) {
+                case NORMAL:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            System.arraycopy(src, 0, result, 0, 4);
+                            return result;
+                        }
+                    };
+                case ADD:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 4; i++) {
+                                result[i] = Math.min(255, src[i] + dst[i]);
+                            }
+                            return result;
+                        }
+                    };
+                case AVERAGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = (src[i] + dst[i]) >> 1;
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case BLUE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            System.arraycopy(dst, 0, result, 0, 3);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case COLOR:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(srcHSL[0], srcHSL[1], dstHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case COLOR_BURN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = src[i] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - dst[i]) << 8) / src[i]));
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case COLOR_DODGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = src[i] == 255 ? 255 :
+                                    Math.min((dst[i] << 8) / (255 - src[i]), 255);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case DARKEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = Math.min(src[i], dst[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case DIFFERENCE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case EXCLUSION:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case FREEZE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = src[i] == 0 ? 0 :
+                                    Math.max(0, 255 - (255 - dst[i]) * (255 - dst[i]) / src[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case GLOW:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] == 255 ? 255 :
+                                    Math.min(255, src[i] * src[i] / (255 - dst[i]));
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case GREEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0],
+                                dst[1],
+                                src[2],
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case HARD_LIGHT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                src[0] < 128 ? dst[0] * src[0] >> 7 :
+                                    255 - ((255 - src[0]) * (255 - dst[0]) >> 7),
+                                src[1] < 128 ? dst[1] * src[1] >> 7 :
+                                    255 - ((255 - src[1]) * (255 - dst[1]) >> 7),
+                                src[2] < 128 ? dst[2] * src[2] >> 7 :
+                                    255 - ((255 - src[2]) * (255 - dst[2]) >> 7),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case HEAT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] == 0 ? 0 : Math.max(0, 255 - (255 - src[0]) * (255 - src[0]) / dst[0]),
+                                dst[1] == 0 ? 0 : Math.max(0, 255 - (255 - src[1]) * (255 - src[1]) / dst[1]),
+                                dst[2] == 0 ? 0 : Math.max(0, 255 - (255 - src[2]) * (255 - src[2]) / dst[2]),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case HUE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(srcHSL[0], dstHSL[1], dstHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case INVERSE_COLOR_BURN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - src[0]) << 8) / dst[0])),
+                                dst[1] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - src[1]) << 8) / dst[1])),
+                                dst[2] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - src[2]) << 8) / dst[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case INVERSE_COLOR_DODGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] == 255 ? 255 :
+                                    Math.min((src[0] << 8) / (255 - dst[0]), 255),
+                                dst[1] == 255 ? 255 :
+                                    Math.min((src[1] << 8) / (255 - dst[1]), 255),
+                                dst[2] == 255 ? 255 :
+                                    Math.min((src[2] << 8) / (255 - dst[2]), 255),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case LIGHTEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = Math.max(src[i], dst[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case LUMINOSITY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(dstHSL[0], dstHSL[1], srcHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case MULTIPLY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = (src[i] * dst[i]) >> 8;
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case NEGATION:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                255 - Math.abs(255 - dst[0] - src[0]),
+                                255 - Math.abs(255 - dst[1] - src[1]),
+                                255 - Math.abs(255 - dst[2] - src[2]),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case OVERLAY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 :
+                                    255 - ((255 - dst[i]) * (255 - src[i]) >> 7);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case RED:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                src[0],
+                                dst[1],
+                                dst[2],
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case REFLECT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                src[0] == 255 ? 255 : Math.min(255, dst[0] * dst[0] / (255 - src[0])),
+                                src[1] == 255 ? 255 : Math.min(255, dst[1] * dst[1] / (255 - src[1])),
+                                src[2] == 255 ? 255 : Math.min(255, dst[2] * dst[2] / (255 - src[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SATURATION:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(dstHSL[0], srcHSL[1], dstHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case SCREEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                255 - ((255 - src[0]) * (255 - dst[0]) >> 8),
+                                255 - ((255 - src[1]) * (255 - dst[1]) >> 8),
+                                255 - ((255 - src[2]) * (255 - dst[2]) >> 8),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SOFT_BURN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] + src[0] < 256 ?
+	                                (dst[0] == 255 ? 255 :
+                                        Math.min(255, (src[0] << 7) / (255 - dst[0]))) :
+                                            Math.max(0, 255 - (((255 - dst[0]) << 7) / src[0])),
+                                dst[1] + src[1] < 256 ?
+	                                (dst[1] == 255 ? 255 :
+                                        Math.min(255, (src[1] << 7) / (255 - dst[1]))) :
+                                            Math.max(0, 255 - (((255 - dst[1]) << 7) / src[1])),
+                                dst[2] + src[2] < 256 ?
+	                                (dst[2] == 255 ? 255 :
+                                        Math.min(255, (src[2] << 7) / (255 - dst[2]))) :
+                                            Math.max(0, 255 - (((255 - dst[2]) << 7) / src[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SOFT_DODGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] + src[0] < 256 ?
+                                    (src[0] == 255 ? 255 :
+                                        Math.min(255, (dst[0] << 7) / (255 - src[0]))) :
+                                            Math.max(0, 255 - (((255 - src[0]) << 7) / dst[0])),
+                                dst[1] + src[1] < 256 ?
+                                    (src[1] == 255 ? 255 :
+                                        Math.min(255, (dst[1] << 7) / (255 - src[1]))) :
+                                            Math.max(0, 255 - (((255 - src[1]) << 7) / dst[1])),
+                                dst[2] + src[2] < 256 ?
+                                    (src[2] == 255 ? 255 :
+                                        Math.min(255, (dst[2] << 7) / (255 - src[2]))) :
+                                            Math.max(0, 255 - (((255 - src[2]) << 7) / dst[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SOFT_LIGHT:
+                    break;
+                case STAMP:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                Math.max(0, Math.min(255, dst[0] + 2 * src[0] - 256)),
+                                Math.max(0, Math.min(255, dst[1] + 2 * src[1] - 256)),
+                                Math.max(0, Math.min(255, dst[2] + 2 * src[2] - 256)),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SUBTRACT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                Math.max(0, src[0] + dst[0] - 256),
+                                Math.max(0, src[1] + dst[1] - 256),
+                                Math.max(0, src[2] + dst[2] - 256),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+            }
+            throw new IllegalArgumentException("Blender not implement for " +
+                                               composite.getMode().name());
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 7c8ef70..2ff0fc1 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -20,6 +20,7 @@
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.graphics.Bitmap.Config;
@@ -534,7 +535,8 @@
                 // set the color
                 graphics.setColor(new Color(color, true /*alpha*/));
 
-                Composite composite = PorterDuffXfermode_Delegate.getComposite(mode, 0xFF);
+                Composite composite = PorterDuffUtility.getComposite(
+                        PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
                 if (composite != null) {
                     graphics.setComposite(composite);
                 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
index ee90595..4ac376c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -21,11 +21,10 @@
 
 import android.graphics.PorterDuff.Mode;
 
-import java.awt.AlphaComposite;
 import java.awt.Graphics2D;
 import java.awt.image.BufferedImage;
 
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getComposite;
 import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
 
 /**
@@ -57,7 +56,7 @@
 
     @Override
     public boolean isSupported() {
-        return getAlphaCompositeRule(mMode) != -1;
+        return true;
     }
 
     @Override
@@ -68,7 +67,7 @@
     @Override
     public void applyFilter(Graphics2D g, int width, int height) {
         BufferedImage image = createFilterImage(width, height);
-        g.setComposite(getComposite());
+        g.setComposite(getComposite(mMode, 0xFF));
         g.drawImage(image, 0, 0, null);
     }
 
@@ -101,49 +100,36 @@
         return image;
     }
 
-    private AlphaComposite getComposite() {
-        return AlphaComposite.getInstance(getAlphaCompositeRule(mMode));
-    }
-
     // For filtering the colors, the src image should contain the "color" only for pixel values
     // which are not transparent in the target image. But, we are using a simple rectangular image
-    // completely filled with color. Hence some AlphaComposite rules do not apply as intended.
-    // However, in such cases, they can usually be mapped to some other mode, which produces an
+    // completely filled with color. Hence some Composite rules do not apply as intended. However,
+    // in such cases, they can usually be mapped to some other mode, which produces an
     // equivalent result.
     private Mode getCompatibleMode(Mode mode) {
         Mode m = mode;
+        // Modes that are directly supported:
+        // CLEAR, DST, SRC_IN, DST_IN, DST_OUT, SRC_ATOP, DARKEN, LIGHTEN, MULTIPLY, SCREEN,
+        // ADD, OVERLAY
         switch (mode) {
-            // Modes that are directly supported.
-            case CLEAR:
-            case DST:
-            case SRC_IN:
-            case DST_IN:
-            case DST_OUT:
-            case SRC_ATOP:
-                break;
-            // Modes that can be mapped to one of the supported modes.
-            case SRC:
-                m = Mode.SRC_IN;
-                break;
-            case SRC_OVER:
-                m = Mode.SRC_ATOP;
-                break;
-            case DST_OVER:
-                m = Mode.DST;
-                break;
-            case SRC_OUT:
-                m = Mode.CLEAR;
-                break;
-            case DST_ATOP:
-                m = Mode.DST_IN;
-                break;
-            case XOR:
-                m = Mode.DST_OUT;
-                break;
-            // This mode is not supported, but used by Action Bar Overflow Popup Menus. We map this
-            // to the closest supported mode, to prevent showing excessive warnings to the user.
-            case MULTIPLY:
-                m = Mode.SRC_IN;
+        // Modes that can be mapped to one of the supported modes.
+        case SRC:
+            m = Mode.SRC_IN;
+            break;
+        case SRC_OVER:
+            m = Mode.SRC_ATOP;
+            break;
+        case DST_OVER:
+            m = Mode.DST;
+            break;
+        case SRC_OUT:
+            m = Mode.CLEAR;
+            break;
+        case DST_ATOP:
+            m = Mode.DST_IN;
+            break;
+        case XOR:
+            m = Mode.DST_OUT;
+            break;
         }
         return m;
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
index f6c36b6..8825f84 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
@@ -16,17 +16,14 @@
 
 package android.graphics;
 
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.graphics.PorterDuff.Mode;
 
-import java.awt.AlphaComposite;
 import java.awt.Composite;
 
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
 import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
 
 /**
@@ -58,7 +55,7 @@
 
     @Override
     public Composite getComposite(int alpha) {
-        return getComposite(mMode, alpha);
+        return PorterDuffUtility.getComposite(mMode, alpha);
     }
 
     @Override
@@ -72,9 +69,6 @@
         return null;
     }
 
-    public static Composite getComposite(int mode, int alpha) {
-        return getComposite(getPorterDuffMode(mode), alpha);
-    }
 
     // ---- native methods ----
 
@@ -90,17 +84,4 @@
         mMode = getPorterDuffMode(mode);
     }
 
-    private static Composite getComposite(Mode mode, int alpha255) {
-        float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
-        int rule = getAlphaCompositeRule(mode);
-        if (rule >= 0) {
-            return AlphaComposite.getInstance(rule, alpha1);
-        }
-
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
-                String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
-                null, null /*data*/);
-
-        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
-    }
 }
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 2f40003..6927b26 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -72,7 +72,7 @@
 
     @Override
     public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
-            boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9)
+            boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10)
             throws RemoteException {
         // TODO Auto-generated method stub
 
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
similarity index 60%
rename from tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
rename to tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
index 112250d..607e628 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
@@ -14,24 +14,23 @@
  * limitations under the License.
  */
 
-package android.content.res;
+package com.android.layoutlib.bridge.android;
 
 import java.util.Locale;
 
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 import com.ibm.icu.util.ULocale;
 
 /**
- * Delegate used to provide new implementation of a select few methods of {@link Resources}
+ * This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag}
+ * which is only available after Java 6.
  *
- * Through the layoutlib_create tool, the original  methods of Resources have been replaced
- * by calls to methods of the same name in this delegate class.
- *
+ * The create tool re-writes references to the above mentioned method to this one. Hence it's
+ * imperative that this class is not deleted unless the create tool is modified.
  */
-public class Resources_Delegate {
+@SuppressWarnings("UnusedDeclaration")
+public class AndroidLocale {
 
-    @LayoutlibDelegate
-    /*package*/ static String localeToLanguageTag(Resources res, Locale locale)  {
+    public static String toLanguageTag(Locale locale)  {
         return ULocale.forLocale(locale).toLanguageTag();
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index ca61ffb..70a7be8 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1045,6 +1045,12 @@
     }
 
     @Override
+    public File getCodeCacheDir() {
+        // pass
+        return null;
+    }
+
+    @Override
     public File getExternalCacheDir() {
         // pass
         return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
index bc53e93..9588035 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
@@ -19,11 +19,14 @@
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
 
+import android.graphics.BlendComposite;
+import android.graphics.BlendComposite.BlendingMode;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter_Delegate;
 import android.graphics.PorterDuffXfermode_Delegate;
 
 import java.awt.AlphaComposite;
+import java.awt.Composite;
 
 /**
  * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link
@@ -51,46 +54,54 @@
     }
 
     /**
-     * A utility method to convert the porterDuffMode to an int to be used as a rule for {@link
-     * AlphaComposite}. If {@code AlphaComposite} doesn't support the mode, -1 is returned.
+     * A utility method to get the {@link Composite} that represents the filter for the given
+     * PorterDuff mode and the alpha. Defaults to {@link Mode#SRC_OVER} for invalid modes.
      */
-    public static int getAlphaCompositeRule(Mode porterDuffMode) {
-        switch (porterDuffMode) {
+    public static Composite getComposite(Mode mode, int alpha255) {
+        float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
+        switch (mode) {
             case CLEAR:
-                return AlphaComposite.CLEAR;
-            case DARKEN:
-                break;
-            case DST:
-                return AlphaComposite.DST;
-            case DST_ATOP:
-                return AlphaComposite.DST_ATOP;
-            case DST_IN:
-                return AlphaComposite.DST_IN;
-            case DST_OUT:
-                return AlphaComposite.DST_OUT;
-            case DST_OVER:
-                return AlphaComposite.DST_OVER;
-            case LIGHTEN:
-                break;
-            case MULTIPLY:
-                break;
-            case SCREEN:
-                break;
+                return AlphaComposite.getInstance(AlphaComposite.CLEAR, alpha1);
             case SRC:
-                return AlphaComposite.SRC;
-            case SRC_ATOP:
-                return AlphaComposite.SRC_ATOP;
-            case SRC_IN:
-                return AlphaComposite.SRC_IN;
-            case SRC_OUT:
-                return AlphaComposite.SRC_OUT;
+                return AlphaComposite.getInstance(AlphaComposite.SRC, alpha1);
+            case DST:
+                return AlphaComposite.getInstance(AlphaComposite.DST, alpha1);
             case SRC_OVER:
-                return AlphaComposite.SRC_OVER;
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
+            case DST_OVER:
+                return AlphaComposite.getInstance(AlphaComposite.DST_OVER, alpha1);
+            case SRC_IN:
+                return AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha1);
+            case DST_IN:
+                return AlphaComposite.getInstance(AlphaComposite.DST_IN, alpha1);
+            case SRC_OUT:
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, alpha1);
+            case DST_OUT:
+                return AlphaComposite.getInstance(AlphaComposite.DST_OUT, alpha1);
+            case SRC_ATOP:
+                return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha1);
+            case DST_ATOP:
+                return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, alpha1);
             case XOR:
-                return AlphaComposite.XOR;
-        }
-        // This is an unsupported mode.
-        return -1;
+                return AlphaComposite.getInstance(AlphaComposite.XOR, alpha1);
+            case DARKEN:
+                return BlendComposite.getInstance(BlendingMode.DARKEN, alpha1);
+            case LIGHTEN:
+                return BlendComposite.getInstance(BlendingMode.LIGHTEN, alpha1);
+            case MULTIPLY:
+                return BlendComposite.getInstance(BlendingMode.MULTIPLY, alpha1);
+            case SCREEN:
+                return BlendComposite.getInstance(BlendingMode.SCREEN, alpha1);
+            case ADD:
+                return BlendComposite.getInstance(BlendingMode.ADD, alpha1);
+            case OVERLAY:
+                return BlendComposite.getInstance(BlendingMode.OVERLAY, alpha1);
+            default:
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+                        String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
+                        null, null /*data*/);
 
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
+        }
     }
 }
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index 8de64db..727b194 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -39,7 +39,8 @@
 
 The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration
 is done in the main() method and the CreateInfo structure is expected to change with the Android
-platform as new classes are added, changed or removed.
+platform as new classes are added, changed or removed. Some configuration that may be platform
+dependent is also present elsewhere in code.
 
 The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that
 provides all the necessary missing implementation for rendering graphics in Eclipse.
@@ -95,7 +96,7 @@
 - specific classes to refactor.
 
 Each of these are specific strategies we use to be able to modify the Android code to fit within the
-Eclipse renderer. These strategies are explained beow.
+Eclipse renderer. These strategies are explained below.
 
 The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it
 to produce a byte array suitable for the final JAR file.
@@ -130,9 +131,11 @@
 valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on
 Mac has horrible font rendering support.
 
-ReplaceMethodCallsAdapter replaces calls to certain methods. Currently, it only rewrites calls to
-specialized versions of java.lang.System.arraycopy(), which are not part of the Desktop VM to call
-the more general method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V.
+ReplaceMethodCallsAdapter replaces calls to certain methods. This is different from the
+DelegateMethodAdapter since it doesn't preserve the original copy of the method and more importantly
+changes the calls to a method in each class instead of changing the implementation of the method.
+This is useful for methods in the Java namespace where we cannot add delegates. The configuration
+for this is not done through the CreateInfo class, but done in the ReplaceMethodAdapter.
 
 The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7
 Transformation chains in the asm user guide, link in the References.) The order of execution of
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index 767e597..e043d4d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -724,9 +724,8 @@
                 considerDesc(desc);
 
 
-                // Check if method is a specialized version of java.lang.System.arrayCopy()
-                if (owner.equals("java/lang/System") && name.equals("arraycopy")
-                        && !desc.equals("(Ljava/lang/Object;ILjava/lang/Object;II)V")) {
+                // Check if method needs to replaced by a call to a different method.
+                if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) {
                     mReplaceMethodCallClasses.add(mOwnerClass);
                 }
             }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 552fb6c..8fb8928 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -126,7 +126,6 @@
         "android.content.res.Resources$Theme#obtainStyledAttributes",
         "android.content.res.Resources$Theme#resolveAttribute",
         "android.content.res.Resources$Theme#resolveAttributes",
-        "android.content.res.Resources#localeToLanguageTag",
         "android.content.res.AssetManager#newTheme",
         "android.content.res.AssetManager#deleteTheme",
         "android.content.res.AssetManager#applyThemeStyle",
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index ae17417..0b5fb46 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -20,12 +20,15 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
- * Replaces calls to certain methods that do not exist in the Desktop VM.
+ * Replaces calls to certain methods that do not exist in the Desktop VM. Useful for methods in the
+ * "java" package.
  */
 public class ReplaceMethodCallsAdapter extends ClassVisitor {
 
@@ -37,6 +40,60 @@
             "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
             "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
 
+    private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(2);
+
+    // Static initialization block to initialize METHOD_REPLACERS.
+    static {
+        // Case 1: java.lang.System.arraycopy()
+        METHOD_REPLACERS.add(new MethodReplacer() {
+            @Override
+            public boolean isNeeded(String owner, String name, String desc) {
+                return owner.equals("java/lang/System") && name.equals("arraycopy") &&
+                        ARRAYCOPY_DESCRIPTORS.contains(desc);
+            }
+
+            @Override
+            public void replace(int opcode, String owner, String name, String desc,
+                    int[] opcodeOut, String[] output) {
+                assert isNeeded(owner, name, desc) && output.length == 3
+                        && opcodeOut.length == 1;
+                opcodeOut[0] = opcode;
+                output[0] = owner;
+                output[1] = name;
+                output[2] = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+            }
+        });
+
+        // Case 2: java.util.Locale.toLanguageTag()
+        METHOD_REPLACERS.add(new MethodReplacer() {
+            @Override
+            public boolean isNeeded(String owner, String name, String desc) {
+                return owner.equals("java/util/Locale") && name.equals("toLanguageTag") &&
+                        "()Ljava/lang/String;".equals(desc);
+            }
+
+            @Override
+            public void replace(int opcode, String owner, String name, String desc,
+                    int[] opcodeOut, String[] output) {
+                assert isNeeded(owner, name, desc) && output.length == 3
+                        && opcodeOut.length == 1;
+                opcodeOut[0] = Opcodes.INVOKESTATIC;
+                output[0] = "com.android.layoutlib.bridge.android.AndroidLocale";
+                output[1] = name;
+                output[2] = "(Ljava/util/Locale;)Ljava/lang/String;";
+            }
+        });
+    }
+
+    public static boolean isReplacementNeeded(String owner, String name, String desc) {
+        for (MethodReplacer replacer : METHOD_REPLACERS) {
+            if (replacer.isNeeded(owner, name, desc)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public ReplaceMethodCallsAdapter(ClassVisitor cv) {
         super(Opcodes.ASM4, cv);
     }
@@ -56,13 +113,34 @@
         @Override
         public void visitMethodInsn(int opcode, String owner, String name, String desc) {
             // Check if method is a specialized version of java.lang.System.arrayCopy
-            if (owner.equals("java/lang/System") && name.equals("arraycopy")) {
-
-                if (ARRAYCOPY_DESCRIPTORS.contains(desc)) {
-                    desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+            for (MethodReplacer replacer : METHOD_REPLACERS) {
+                if (replacer.isNeeded(owner, name, desc)) {
+                    String[] output = new String[3];
+                    int[] opcodeOut = new int[1];
+                    replacer.replace(opcode, owner, name, desc, opcodeOut, output);
+                    opcode = opcodeOut[0];
+                    owner = output[0];
+                    name = output[1];
+                    desc = output[2];
+                    break;
                 }
             }
             super.visitMethodInsn(opcode, owner, name, desc);
         }
     }
+
+    private interface MethodReplacer {
+        public boolean isNeeded(String owner, String name, String desc);
+
+        /**
+         * This method must update the values of the output arrays with the new values of method
+         * attributes - opcode, owner, name and desc.
+         * @param opcodeOut An array that will contain the new value of the opcode. The size of
+         *                  the array must be 1.
+         * @param output An array that will contain the new values of the owner, name and desc in
+         *               that order. The size of the array must be 3.
+         */
+        public void replace(int opcode, String owner, String name, String desc, int[] opcodeOut,
+                String[] output);
+    }
 }