Merge "Revert "Add toString & equals helper methods to PhoneAccount.java""
diff --git a/Android.mk b/Android.mk
index 23c4753..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 \
@@ -320,9 +318,12 @@
 	media/java/android/media/IRemoteVolumeObserver.aidl \
 	media/java/android/media/IRingtonePlayer.aidl \
 	media/java/android/media/IVolumeController.aidl \
-	media/java/android/media/routeprovider/IRouteConnection.aidl \
-	media/java/android/media/routeprovider/IRouteProvider.aidl \
-	media/java/android/media/routeprovider/IRouteProviderCallback.aidl \
+	media/java/android/media/routing/IMediaRouteService.aidl \
+	media/java/android/media/routing/IMediaRouteClientCallback.aidl \
+	media/java/android/media/routing/IMediaRouter.aidl \
+	media/java/android/media/routing/IMediaRouterDelegate.aidl \
+	media/java/android/media/routing/IMediaRouterRoutingCallback.aidl \
+	media/java/android/media/routing/IMediaRouterStateCallback.aidl \
 	media/java/android/media/session/IActiveSessionsListener.aidl \
 	media/java/android/media/session/ISessionController.aidl \
 	media/java/android/media/session/ISessionControllerCallback.aidl \
@@ -333,12 +334,11 @@
 	media/java/android/media/tv/ITvInputHardware.aidl \
 	media/java/android/media/tv/ITvInputHardwareCallback.aidl \
 	media/java/android/media/tv/ITvInputManager.aidl \
+	media/java/android/media/tv/ITvInputManagerCallback.aidl \
 	media/java/android/media/tv/ITvInputService.aidl \
 	media/java/android/media/tv/ITvInputServiceCallback.aidl \
 	media/java/android/media/tv/ITvInputSession.aidl \
 	media/java/android/media/tv/ITvInputSessionCallback.aidl \
-	telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl \
-	telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl \
 	telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl \
 	telecomm/java/com/android/internal/telecomm/ICallVideoClient.aidl \
 	telecomm/java/com/android/internal/telecomm/IConnectionService.aidl \
@@ -359,10 +359,6 @@
 	telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	telephony/java/com/android/internal/telephony/ITelephony.aidl \
-	telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl \
-	telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl \
-	telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl \
-	telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl \
 	telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
 	telephony/java/com/android/internal/telephony/ISms.aidl \
 	telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
@@ -527,6 +523,7 @@
 	frameworks/base/location/java/com/android/internal/location/ProviderRequest.aidl \
 	frameworks/base/media/java/android/media/MediaMetadata.aidl \
 	frameworks/base/media/java/android/media/Rating.aidl \
+	frameworks/base/media/java/android/media/routing/MediaRouteSelector.aidl \
 	frameworks/base/media/java/android/media/session/MediaSession.aidl \
 	frameworks/base/media/java/android/media/session/PlaybackState.aidl \
 	frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
diff --git a/api/current.txt b/api/current.txt
index 41aad5f..9ce7c1f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23,6 +23,7 @@
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
     field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
     field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
+    field public static final java.lang.String BIND_MEDIA_ROUTE_SERVICE = "android.permission.BIND_MEDIA_ROUTE_SERVICE";
     field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
@@ -358,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
@@ -446,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
@@ -741,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
@@ -995,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
@@ -1367,6 +1380,7 @@
     field public static final int windowContentTransitions = 16843794; // 0x1010412
     field public static final int windowDisablePreview = 16843298; // 0x1010222
     field public static final int windowDrawsSystemBarBackgrounds = 16843858; // 0x1010452
+    field public static final int windowElevation = 16843922; // 0x1010492
     field public static final int windowEnableSplitTouch = 16843543; // 0x1010317
     field public static final int windowEnterAnimation = 16842932; // 0x10100b4
     field public static final int windowEnterTransition = 16843833; // 0x1010439
@@ -3308,6 +3322,7 @@
     method public android.view.LayoutInflater getLayoutInflater();
     method public android.app.LoaderManager getLoaderManager();
     method public java.lang.String getLocalClassName();
+    method public final android.media.session.MediaController getMediaController();
     method public android.view.MenuInflater getMenuInflater();
     method public final android.app.Activity getParent();
     method public android.content.Intent getParentActivityIntent();
@@ -3431,6 +3446,7 @@
     method public void setFinishOnTouchOutside(boolean);
     method public void setImmersive(boolean);
     method public void setIntent(android.content.Intent);
+    method public final void setMediaController(android.media.session.MediaController);
     method public boolean setMediaPlaying(boolean);
     method public final void setProgress(int);
     method public final void setProgressBarIndeterminate(boolean);
@@ -3509,6 +3525,7 @@
     method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException;
     method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
     method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException;
+    method public boolean isInLockTaskMode();
     method public boolean isLowRamDevice();
     method public static boolean isRunningInTestHarness();
     method public static boolean isUserAMonkey();
@@ -3659,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>...);
@@ -5287,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);
@@ -6570,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 {
@@ -7045,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);
@@ -7057,6 +7077,7 @@
     method public abstract java.io.File getFileStreamPath(java.lang.String);
     method public abstract java.io.File getFilesDir();
     method public abstract android.os.Looper getMainLooper();
+    method public abstract java.io.File getNoBackupFilesDir();
     method public abstract java.io.File getObbDir();
     method public abstract java.io.File[] getObbDirs();
     method public abstract java.lang.String getPackageCodePath();
@@ -7215,6 +7236,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);
@@ -7226,6 +7248,7 @@
     method public java.io.File getFileStreamPath(java.lang.String);
     method public java.io.File getFilesDir();
     method public android.os.Looper getMainLooper();
+    method public java.io.File getNoBackupFilesDir();
     method public java.io.File getObbDir();
     method public java.io.File[] getObbDirs();
     method public java.lang.String getPackageCodePath();
@@ -7494,6 +7517,7 @@
     field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
     field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
+    field public static final java.lang.String ACTION_CONFIRM_DEVICE_CREDENTIAL = "android.intent.action.CONFIRM_DEVICE_CREDENTIAL";
     field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
     field public static final java.lang.String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
     field public static final java.lang.String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
@@ -7636,6 +7660,7 @@
     field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
     field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
+    field public static final java.lang.String EXTRA_DETAILS = "android.intent.extra.DETAILS";
     field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
     field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
     field public static final int EXTRA_DOCK_STATE_DESK = 1; // 0x1
@@ -7689,7 +7714,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
@@ -7917,6 +7941,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[]);
@@ -7951,31 +7976,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
@@ -7988,11 +8016,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[]);
@@ -8351,6 +8374,36 @@
     field public int reqGlEsVersion;
   }
 
+  public class InstallSessionInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.graphics.Bitmap getIcon();
+    method public java.lang.String getInstallerPackageName();
+    method public java.lang.String getPackageName();
+    method public int getProgress();
+    method public int getSessionId();
+    method public java.lang.CharSequence getTitle();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public class InstallSessionParams implements android.os.Parcelable {
+    ctor public InstallSessionParams();
+    method public int describeContents();
+    method public void setDeltaSize(long);
+    method public void setIcon(android.graphics.Bitmap);
+    method public void setInstallLocation(int);
+    method public void setModeFullInstall();
+    method public void setModeInheritExisting();
+    method public void setOriginatingUri(android.net.Uri);
+    method public void setPackageName(java.lang.String);
+    method public void setProgressMax(int);
+    method public void setReferrerUri(android.net.Uri);
+    method public void setSignatures(android.content.pm.Signature[]);
+    method public void setTitle(java.lang.CharSequence);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
   public class InstrumentationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     ctor public InstrumentationInfo();
     ctor public InstrumentationInfo(android.content.pm.InstrumentationInfo);
@@ -8366,6 +8419,9 @@
     field public java.lang.String targetPackage;
   }
 
+  public class KeySet {
+  }
+
   public class LabeledIntent extends android.content.Intent {
     ctor public LabeledIntent(android.content.Intent, java.lang.String, int, int);
     ctor public LabeledIntent(android.content.Intent, java.lang.String, java.lang.CharSequence, int);
@@ -8415,6 +8471,9 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int INSTALL_LOCATION_AUTO = 0; // 0x0
+    field public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1; // 0x1
+    field public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; // 0x2
     field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2
     field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1
     field public android.content.pm.ActivityInfo[] activities;
@@ -8422,6 +8481,7 @@
     field public android.content.pm.ConfigurationInfo[] configPreferences;
     field public long firstInstallTime;
     field public int[] gids;
+    field public int installLocation;
     field public android.content.pm.InstrumentationInfo[] instrumentation;
     field public long lastUpdateTime;
     field public java.lang.String packageName;
@@ -8435,10 +8495,52 @@
     field public java.lang.String sharedUserId;
     field public int sharedUserLabel;
     field public android.content.pm.Signature[] signatures;
+    field public java.lang.String[] splitNames;
     field public int versionCode;
     field public java.lang.String versionName;
   }
 
+  public class PackageInstaller {
+    method public int createSession(android.content.pm.InstallSessionParams) throws java.io.IOException;
+    method public java.util.List<android.content.pm.InstallSessionInfo> getActiveSessions();
+    method public android.content.pm.PackageInstaller.Session openSession(int);
+    method public void registerSessionObserver(android.content.pm.PackageInstaller.SessionObserver);
+    method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallResultCallback);
+    method public void unregisterSessionObserver(android.content.pm.PackageInstaller.SessionObserver);
+  }
+
+  public static abstract class PackageInstaller.CommitResultCallback {
+    ctor public PackageInstaller.CommitResultCallback();
+    method public abstract void onFailure(java.lang.String);
+    method public void onFailureConflict(java.lang.String, java.lang.String);
+    method public void onFailureIncompatible(java.lang.String);
+    method public void onFailureInvalid(java.lang.String);
+    method public void onFailureStorage(java.lang.String);
+    method public abstract void onSuccess();
+  }
+
+  public static class PackageInstaller.Session implements java.io.Closeable {
+    method public void close();
+    method public void commit(android.content.pm.PackageInstaller.CommitResultCallback);
+    method public void destroy();
+    method public void fsync(java.io.OutputStream) throws java.io.IOException;
+    method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
+    method public void setProgress(int);
+  }
+
+  public static abstract class PackageInstaller.SessionObserver {
+    ctor public PackageInstaller.SessionObserver();
+    method public abstract void onCreated(android.content.pm.InstallSessionInfo);
+    method public abstract void onFinalized(int, boolean);
+    method public abstract void onProgress(int, int);
+  }
+
+  public static abstract class PackageInstaller.UninstallResultCallback {
+    ctor public PackageInstaller.UninstallResultCallback();
+    method public abstract void onFailure(java.lang.String);
+    method public abstract void onSuccess();
+  }
+
   public class PackageItemInfo {
     ctor public PackageItemInfo();
     ctor public PackageItemInfo(android.content.pm.PackageItemInfo);
@@ -8501,8 +8603,10 @@
     method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method public abstract android.content.pm.PackageInstaller getInstaller();
     method public abstract java.lang.String getInstallerPackageName(java.lang.String);
     method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public abstract android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
     method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
     method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
     method public abstract java.lang.String getNameForUid(int);
@@ -8521,12 +8625,15 @@
     method public abstract android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public abstract android.content.pm.KeySet getSigningKeySet(java.lang.String);
     method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
     method public abstract java.lang.String[] getSystemSharedLibraryNames();
     method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public abstract boolean hasSystemFeature(java.lang.String);
     method public abstract boolean isSafeMode();
+    method public abstract boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
+    method public abstract boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
     method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
     method public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
     method public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
@@ -8583,12 +8690,14 @@
     field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
     field public static final java.lang.String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
     field public static final java.lang.String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
+    field public static final java.lang.String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature";
     field public static final java.lang.String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
     field public static final java.lang.String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass";
     field public static final java.lang.String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
     field public static final java.lang.String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate";
     field public static final java.lang.String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
     field public static final java.lang.String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
+    field public static final java.lang.String FEATURE_SENSOR_RELATIVE_HUMIDITY = "android.hardware.sensor.relative_humidity";
     field public static final java.lang.String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
     field public static final java.lang.String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
     field public static final java.lang.String FEATURE_SIP = "android.software.sip";
@@ -8808,7 +8917,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;
@@ -11857,7 +11966,7 @@
 
 package android.hardware {
 
-  public class Camera {
+  public deprecated class Camera {
     method public final void addCallbackBuffer(byte[]);
     method public final void autoFocus(android.hardware.Camera.AutoFocusCallback);
     method public final void cancelAutoFocus();
@@ -11896,21 +12005,21 @@
     field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1
   }
 
-  public static class Camera.Area {
+  public static deprecated class Camera.Area {
     ctor public Camera.Area(android.graphics.Rect, int);
     field public android.graphics.Rect rect;
     field public int weight;
   }
 
-  public static abstract interface Camera.AutoFocusCallback {
+  public static abstract deprecated interface Camera.AutoFocusCallback {
     method public abstract void onAutoFocus(boolean, android.hardware.Camera);
   }
 
-  public static abstract interface Camera.AutoFocusMoveCallback {
+  public static abstract deprecated interface Camera.AutoFocusMoveCallback {
     method public abstract void onAutoFocusMoving(boolean, android.hardware.Camera);
   }
 
-  public static class Camera.CameraInfo {
+  public static deprecated class Camera.CameraInfo {
     ctor public Camera.CameraInfo();
     field public static final int CAMERA_FACING_BACK = 0; // 0x0
     field public static final int CAMERA_FACING_FRONT = 1; // 0x1
@@ -11919,11 +12028,11 @@
     field public int orientation;
   }
 
-  public static abstract interface Camera.ErrorCallback {
+  public static abstract deprecated interface Camera.ErrorCallback {
     method public abstract void onError(int, android.hardware.Camera);
   }
 
-  public static class Camera.Face {
+  public static deprecated class Camera.Face {
     ctor public Camera.Face();
     field public int id;
     field public android.graphics.Point leftEye;
@@ -11933,15 +12042,15 @@
     field public int score;
   }
 
-  public static abstract interface Camera.FaceDetectionListener {
+  public static abstract deprecated interface Camera.FaceDetectionListener {
     method public abstract void onFaceDetection(android.hardware.Camera.Face[], android.hardware.Camera);
   }
 
-  public static abstract interface Camera.OnZoomChangeListener {
+  public static abstract deprecated interface Camera.OnZoomChangeListener {
     method public abstract void onZoomChange(int, boolean, android.hardware.Camera);
   }
 
-  public class Camera.Parameters {
+  public deprecated class Camera.Parameters {
     method public java.lang.String flatten();
     method public java.lang.String get(java.lang.String);
     method public java.lang.String getAntibanding();
@@ -12091,19 +12200,19 @@
     field public static final java.lang.String WHITE_BALANCE_WARM_FLUORESCENT = "warm-fluorescent";
   }
 
-  public static abstract interface Camera.PictureCallback {
+  public static abstract deprecated interface Camera.PictureCallback {
     method public abstract void onPictureTaken(byte[], android.hardware.Camera);
   }
 
-  public static abstract interface Camera.PreviewCallback {
+  public static abstract deprecated interface Camera.PreviewCallback {
     method public abstract void onPreviewFrame(byte[], android.hardware.Camera);
   }
 
-  public static abstract interface Camera.ShutterCallback {
+  public static abstract deprecated interface Camera.ShutterCallback {
     method public abstract void onShutter();
   }
 
-  public class Camera.Size {
+  public deprecated class Camera.Size {
     ctor public Camera.Size(int, int);
     field public int height;
     field public int width;
@@ -14324,6 +14433,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);
@@ -14846,7 +14956,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();
@@ -14864,6 +14976,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);
@@ -14970,7 +15083,7 @@
     method public void setAudioEncodingBitRate(int);
     method public void setAudioSamplingRate(int);
     method public void setAudioSource(int) throws java.lang.IllegalStateException;
-    method public void setCamera(android.hardware.Camera);
+    method public deprecated void setCamera(android.hardware.Camera);
     method public void setCaptureRate(double);
     method public void setLocation(float, float);
     method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
@@ -15871,12 +15984,249 @@
 
 }
 
+package android.media.routing {
+
+  public final class MediaRouteSelector implements android.os.Parcelable {
+    method public boolean containsProtocol(java.lang.Class<?>);
+    method public boolean containsProtocol(java.lang.String);
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public int getOptionalFeatures();
+    method public java.util.List<java.lang.String> getOptionalProtocols();
+    method public int getRequiredFeatures();
+    method public java.util.List<java.lang.String> getRequiredProtocols();
+    method public java.lang.String getServicePackageName();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public static final class MediaRouteSelector.Builder {
+    ctor public MediaRouteSelector.Builder();
+    method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.Class<?>);
+    method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.String);
+    method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.Class<?>);
+    method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.String);
+    method public android.media.routing.MediaRouteSelector build();
+    method public android.media.routing.MediaRouteSelector.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouteSelector.Builder setOptionalFeatures(int);
+    method public android.media.routing.MediaRouteSelector.Builder setRequiredFeatures(int);
+    method public android.media.routing.MediaRouteSelector.Builder setServicePackageName(java.lang.String);
+  }
+
+  public abstract class MediaRouteService extends android.app.Service {
+    ctor public MediaRouteService();
+    method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract android.media.routing.MediaRouteService.ClientSession onCreateClientSession(android.media.routing.MediaRouteService.ClientInfo);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.media.routing.MediaRouteService";
+  }
+
+  public static final class MediaRouteService.ClientInfo {
+    method public java.lang.String getPackageName();
+    method public int getUid();
+  }
+
+  public static abstract class MediaRouteService.ClientSession {
+    ctor public MediaRouteService.ClientSession();
+    method public abstract boolean onConnect(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouteService.ConnectionCallback);
+    method public abstract void onDisconnect();
+    method public void onPauseStream();
+    method public void onRelease();
+    method public void onResumeStream();
+    method public abstract boolean onStartDiscovery(android.media.routing.MediaRouter.DiscoveryRequest, android.media.routing.MediaRouteService.DiscoveryCallback);
+    method public abstract void onStopDiscovery();
+  }
+
+  public final class MediaRouteService.ConnectionCallback {
+    method public void onConnected(android.media.routing.MediaRouter.ConnectionInfo);
+    method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle);
+    method public void onDisconnected();
+  }
+
+  public final class MediaRouteService.DiscoveryCallback {
+    method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>);
+    method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo);
+    method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle);
+  }
+
+  public final class MediaRouter {
+    ctor public MediaRouter(android.content.Context);
+    method public void addSelector(android.media.routing.MediaRouteSelector);
+    method public void clearSelectors();
+    method public android.media.routing.MediaRouter.Delegate createDelegate();
+    method public android.media.routing.MediaRouter.ConnectionInfo getConnection();
+    method public int getConnectionState();
+    method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations();
+    method public java.util.List<android.media.routing.MediaRouter.RouteInfo> getDiscoveredRoutes(android.media.routing.MediaRouter.DestinationInfo);
+    method public int getDiscoveryState();
+    method public android.media.AudioAttributes getPreferredAudioAttributes();
+    method public android.view.Display getPreferredPresentationDisplay();
+    method public android.media.VolumeProvider getPreferredVolumeProvider();
+    method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination();
+    method public android.media.routing.MediaRouter.RouteInfo getSelectedRoute();
+    method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors();
+    method public boolean isReleased();
+    method public void pauseStream();
+    method public void release();
+    method public void removeSelector(android.media.routing.MediaRouteSelector);
+    method public void resumeStream();
+    method public void setRoutingCallback(android.media.routing.MediaRouter.RoutingCallback, android.os.Handler);
+    field public static final int CONNECTION_ERROR_ABORTED = 1; // 0x1
+    field public static final int CONNECTION_ERROR_BARGED = 7; // 0x7
+    field public static final int CONNECTION_ERROR_BROKEN = 6; // 0x6
+    field public static final int CONNECTION_ERROR_BUSY = 4; // 0x4
+    field public static final int CONNECTION_ERROR_TIMEOUT = 5; // 0x5
+    field public static final int CONNECTION_ERROR_UNAUTHORIZED = 2; // 0x2
+    field public static final int CONNECTION_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int CONNECTION_ERROR_UNREACHABLE = 3; // 0x3
+    field public static final int CONNECTION_FLAG_BARGE = 1; // 0x1
+    field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+    field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+    field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+    field public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0; // 0x0
+    field public static final int DISCONNECTION_REASON_ERROR = 2; // 0x2
+    field public static final int DISCONNECTION_REASON_USER_REQUEST = 1; // 0x1
+    field public static final int DISCOVERY_ERROR_ABORTED = 1; // 0x1
+    field public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2; // 0x2
+    field public static final int DISCOVERY_ERROR_UNKNOWN = 0; // 0x0
+    field public static final int DISCOVERY_FLAG_BACKGROUND = 1; // 0x1
+    field public static final int DISCOVERY_STATE_STARTED = 1; // 0x1
+    field public static final int DISCOVERY_STATE_STOPPED = 0; // 0x0
+    field public static final int ROUTE_FEATURE_LIVE_AUDIO = 1; // 0x1
+    field public static final int ROUTE_FEATURE_LIVE_VIDEO = 2; // 0x2
+  }
+
+  public static final class MediaRouter.ConnectionInfo {
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public android.os.Bundle getExtras();
+    method public int getFeatures();
+    method public android.view.Display getPresentationDisplay();
+    method public android.os.IBinder getProtocolBinder(java.lang.String);
+    method public android.os.IBinder getProtocolBinder(int);
+    method public T getProtocolObject(java.lang.Class<T>);
+    method public java.util.List<java.lang.String> getProtocols();
+    method public android.media.routing.MediaRouter.RouteInfo getRoute();
+    method public android.media.VolumeProvider getVolumeProvider();
+  }
+
+  public static final class MediaRouter.ConnectionInfo.Builder {
+    ctor public MediaRouter.ConnectionInfo.Builder(android.media.routing.MediaRouter.RouteInfo);
+    method public android.media.routing.MediaRouter.ConnectionInfo build();
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setAudioAttributes(android.media.AudioAttributes);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setPresentationDisplay(android.view.Display);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolBinder(java.lang.String, android.os.IBinder);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolStub(java.lang.Class<?>, android.os.IInterface);
+    method public android.media.routing.MediaRouter.ConnectionInfo.Builder setVolumeProvider(android.media.VolumeProvider);
+  }
+
+  public static final class MediaRouter.ConnectionRequest {
+    method public android.os.Bundle getExtras();
+    method public int getFlags();
+    method public android.media.routing.MediaRouter.RouteInfo getRoute();
+    method public void setExtras(android.os.Bundle);
+    method public void setFlags(int);
+    method public void setRoute(android.media.routing.MediaRouter.RouteInfo);
+  }
+
+  public static final class MediaRouter.Delegate {
+    ctor public MediaRouter.Delegate();
+    method public void addStateCallback(android.media.routing.MediaRouter.StateCallback, android.os.Handler);
+    method public void connect(android.media.routing.MediaRouter.DestinationInfo, int);
+    method public void disconnect(int);
+    method public int getConnectionState();
+    method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations();
+    method public int getDiscoveryState();
+    method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination();
+    method public boolean isReleased();
+    method public void removeStateCallback(android.media.routing.MediaRouter.StateCallback);
+    method public void startDiscovery(int);
+    method public void stopDiscovery();
+  }
+
+  public static final class MediaRouter.DestinationInfo {
+    method public java.lang.CharSequence getDescription();
+    method public android.os.Bundle getExtras();
+    method public int getIconResourceId();
+    method public java.lang.String getId();
+    method public java.lang.CharSequence getName();
+    method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata();
+    method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
+  }
+
+  public static final class MediaRouter.DestinationInfo.Builder {
+    ctor public MediaRouter.DestinationInfo.Builder(java.lang.String, android.media.routing.MediaRouter.ServiceMetadata, java.lang.CharSequence);
+    method public android.media.routing.MediaRouter.DestinationInfo build();
+    method public android.media.routing.MediaRouter.DestinationInfo.Builder setDescription(java.lang.CharSequence);
+    method public android.media.routing.MediaRouter.DestinationInfo.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouter.DestinationInfo.Builder setIconResourceId(int);
+  }
+
+  public static final class MediaRouter.DiscoveryRequest {
+    method public int getFlags();
+    method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors();
+    method public void setFlags(int);
+    method public void setSelectors(java.util.List<android.media.routing.MediaRouteSelector>);
+  }
+
+  public static final class MediaRouter.RouteInfo {
+    method public android.media.routing.MediaRouter.DestinationInfo getDestination();
+    method public android.os.Bundle getExtras();
+    method public int getFeatures();
+    method public java.lang.String getId();
+    method public java.util.List<java.lang.String> getProtocols();
+    method public android.media.routing.MediaRouteSelector getSelector();
+  }
+
+  public static final class MediaRouter.RouteInfo.Builder {
+    ctor public MediaRouter.RouteInfo.Builder(java.lang.String, android.media.routing.MediaRouter.DestinationInfo, android.media.routing.MediaRouteSelector);
+    method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.Class<T>);
+    method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.String);
+    method public android.media.routing.MediaRouter.RouteInfo build();
+    method public android.media.routing.MediaRouter.RouteInfo.Builder setExtras(android.os.Bundle);
+    method public android.media.routing.MediaRouter.RouteInfo.Builder setFeatures(int);
+  }
+
+  public static abstract class MediaRouter.RoutingCallback extends android.media.routing.MediaRouter.StateCallback {
+    ctor public MediaRouter.RoutingCallback();
+    method public boolean onPrepareConnectionRequest(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>);
+    method public boolean onPrepareDiscoveryRequest(android.media.routing.MediaRouter.DiscoveryRequest, java.util.List<android.media.routing.MediaRouteSelector>);
+  }
+
+  public static final class MediaRouter.ServiceMetadata {
+    method public android.content.ComponentName getComponentName();
+    method public android.graphics.drawable.Drawable getIcon(android.content.pm.PackageManager);
+    method public java.lang.CharSequence getLabel(android.content.pm.PackageManager);
+    method public java.lang.String getPackageName();
+    method public android.content.pm.ServiceInfo getService();
+  }
+
+  public static abstract class MediaRouter.StateCallback {
+    ctor public MediaRouter.StateCallback();
+    method public void onConnected();
+    method public void onConnecting();
+    method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle);
+    method public void onConnectionStateChanged(int);
+    method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo);
+    method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo);
+    method public void onDisconnected();
+    method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle);
+    method public void onDiscoveryStarted();
+    method public void onDiscoveryStateChanged(int);
+    method public void onDiscoveryStopped();
+    method public void onReleased();
+    method public void onSelectedDestinationChanged(android.media.routing.MediaRouter.DestinationInfo);
+  }
+
+}
+
 package android.media.session {
 
   public final class MediaController {
     method public void addCallback(android.media.session.MediaController.Callback);
     method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
     method public void adjustVolumeBy(int, int);
+    method public 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();
@@ -15931,6 +16281,7 @@
     method public void setActive(boolean);
     method public void setFlags(int);
     method public void setLaunchPendingIntent(android.app.PendingIntent);
+    method public void setMediaRouter(android.media.routing.MediaRouter);
     method public void setMetadata(android.media.MediaMetadata);
     method public void setPlaybackState(android.media.session.PlaybackState);
     method public void setPlaybackToLocal(int);
@@ -15978,19 +16329,14 @@
   }
 
   public final class PlaybackState implements android.os.Parcelable {
-    ctor public PlaybackState();
-    ctor public PlaybackState(android.media.session.PlaybackState);
     method public int describeContents();
     method public long getActions();
     method public long getBufferPosition();
     method public java.lang.CharSequence getErrorMessage();
-    method public float getPlaybackRate();
+    method public long getLastPositionUpdateTime();
+    method public float getPlaybackSpeed();
     method public long getPosition();
     method public int getState();
-    method public void setActions(long);
-    method public void setBufferPosition(long);
-    method public void setErrorMessage(java.lang.CharSequence);
-    method public void setState(int, long, float);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
     field public static final long ACTION_PAUSE = 2L; // 0x2L
@@ -16005,6 +16351,7 @@
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
     field public static final int STATE_BUFFERING = 6; // 0x6
+    field public static final int STATE_CONNECTING = 8; // 0x8
     field public static final int STATE_ERROR = 7; // 0x7
     field public static final int STATE_FAST_FORWARDING = 4; // 0x4
     field public static final int STATE_NONE = 0; // 0x0
@@ -16016,10 +16363,44 @@
     field public static final int STATE_STOPPED = 1; // 0x1
   }
 
+  public static final class PlaybackState.Builder {
+    ctor public PlaybackState.Builder();
+    ctor public PlaybackState.Builder(android.media.session.PlaybackState);
+    method public android.media.session.PlaybackState build();
+    method public android.media.session.PlaybackState.Builder setActions(long);
+    method public android.media.session.PlaybackState.Builder setBufferPosition(long);
+    method public android.media.session.PlaybackState.Builder setErrorMessage(java.lang.CharSequence);
+    method public android.media.session.PlaybackState.Builder setState(int, long, float, long);
+    method public android.media.session.PlaybackState.Builder setState(int, long, float);
+  }
+
 }
 
 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);
@@ -16114,6 +16495,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";
@@ -16164,8 +16546,13 @@
   }
 
   public final class TvInputManager {
-    method public boolean getAvailability(java.lang.String);
+    method public int getInputState(java.lang.String);
     method public java.util.List<android.media.tv.TvInputInfo> getTvInputList();
+    method public void registerListener(android.media.tv.TvInputManager.TvInputListener, android.os.Handler);
+    method public void unregisterListener(android.media.tv.TvInputManager.TvInputListener);
+    field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
+    field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
+    field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
     field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
     field public static final int VIDEO_UNAVAILABLE_REASON_TUNE = 1; // 0x1
     field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
@@ -16174,7 +16561,7 @@
 
   public static abstract class TvInputManager.TvInputListener {
     ctor public TvInputManager.TvInputListener();
-    method public void onAvailabilityChanged(java.lang.String, boolean);
+    method public void onInputStateChanged(java.lang.String, int);
   }
 
   public abstract class TvInputService extends android.app.Service {
@@ -16268,8 +16655,8 @@
     method public void onChannelRetuned(java.lang.String, android.net.Uri);
     method public void onError(java.lang.String, int);
     method public void onTrackInfoChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>);
-    method public void onVideoSizeChanged(java.lang.String, int, int);
     method public void onVideoAvailable(java.lang.String);
+    method public void onVideoSizeChanged(java.lang.String, int, int);
     method public void onVideoUnavailable(java.lang.String, int);
   }
 
@@ -22696,7 +23083,7 @@
     method protected void onDisconnected();
     method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
     method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob);
-    field public static final java.lang.String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO";
+    field public static final java.lang.String EXTRA_PRINTER_INFO = "android.intent.extra.print.EXTRA_PRINTER_INFO";
     field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
     field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
     field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
@@ -24659,11 +25046,13 @@
     field public static final java.lang.String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
     field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
     field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
+    field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+    field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
     field public static final java.lang.String ACTION_INTERNAL_STORAGE_SETTINGS = "android.settings.INTERNAL_STORAGE_SETTINGS";
@@ -25340,6 +25729,7 @@
     field public static final java.lang.String NUMBER = "number";
     field public static final java.lang.String SOURCE_DATA = "source_data";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
+    field public static final java.lang.String TRANSCRIPTION = "transcription";
   }
 
 }
@@ -27593,6 +27983,63 @@
 
 package android.telecomm {
 
+  public final class Call {
+    method public void addListener(android.telecomm.Call.Listener);
+    method public void answer();
+    method public void conference();
+    method public void disconnect();
+    method public android.telecomm.RemoteCallVideoProvider getCallVideoProvider();
+    method public java.util.List<java.lang.String> getCannedTextResponses();
+    method public java.util.List<android.telecomm.Call> getChildren();
+    method public android.telecomm.Call.Details getDetails();
+    method public android.telecomm.Call getParent();
+    method public java.lang.String getRemainingPostDialSequence();
+    method public int getState();
+    method public void hold();
+    method public void phoneAccountClicked();
+    method public void playDtmfTone(char);
+    method public void postDialContinue(boolean);
+    method public void reject(boolean, java.lang.String);
+    method public void removeListener(android.telecomm.Call.Listener);
+    method public void splitFromConference();
+    method public void stopDtmfTone();
+    method public void swapWithBackgroundCall();
+    method public void unhold();
+    field public static final int STATE_ACTIVE = 4; // 0x4
+    field public static final int STATE_DIALING = 1; // 0x1
+    field public static final int STATE_DISCONNECTED = 7; // 0x7
+    field public static final int STATE_HOLDING = 3; // 0x3
+    field public static final int STATE_NEW = 0; // 0x0
+    field public static final int STATE_RINGING = 2; // 0x2
+  }
+
+  public static class Call.Details {
+    method public android.telecomm.PhoneAccount getAccount();
+    method public java.lang.String getCallerDisplayName();
+    method public int getCallerDisplayNamePresentation();
+    method public int getCapabilities();
+    method public long getConnectTimeMillis();
+    method public int getDisconnectCauseCode();
+    method public java.lang.String getDisconnectCauseMsg();
+    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 {
+    ctor public Call.Listener();
+    method public void onCallDestroyed(android.telecomm.Call);
+    method public void onCallVideoProviderChanged(android.telecomm.Call, android.telecomm.RemoteCallVideoProvider);
+    method public void onCannedTextResponsesLoaded(android.telecomm.Call, java.util.List<java.lang.String>);
+    method public void onChildrenChanged(android.telecomm.Call, java.util.List<android.telecomm.Call>);
+    method public void onDetailsChanged(android.telecomm.Call, android.telecomm.Call.Details);
+    method public void onParentChanged(android.telecomm.Call, android.telecomm.Call);
+    method public void onPostDial(android.telecomm.Call, java.lang.String);
+    method public void onPostDialWait(android.telecomm.Call, java.lang.String);
+    method public void onStateChanged(android.telecomm.Call, int);
+  }
+
   public final class CallAudioState implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -27636,39 +28083,10 @@
 
   public class CallPropertyPresentation {
     ctor public CallPropertyPresentation();
-    field public static final int ALLOWED = 0; // 0x0
-    field public static final int PAYPHONE = 3; // 0x3
-    field public static final int RESTRICTED = 1; // 0x1
-    field public static final int UNKNOWN = 2; // 0x2
-  }
-
-  public final class CallServiceDescriptor implements android.os.Parcelable {
-    method public int describeContents();
-    method public java.lang.String getConnectionServiceId();
-    method public int getNetworkType();
-    method public android.content.ComponentName getServiceComponent();
-    method public static android.telecomm.CallServiceDescriptor.Builder newBuilder(android.content.Context);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator CREATOR;
-    field public static final int FLAG_MOBILE = 4; // 0x4
-    field public static final int FLAG_PSTN = 2; // 0x2
-    field public static final int FLAG_WIFI = 1; // 0x1
-  }
-
-  public static class CallServiceDescriptor.Builder {
-    method public android.telecomm.CallServiceDescriptor build();
-    method public android.telecomm.CallServiceDescriptor.Builder setConnectionService(java.lang.Class<? extends android.telecomm.ConnectionService>);
-    method public android.telecomm.CallServiceDescriptor.Builder setNetworkType(int);
-  }
-
-  public final class CallServiceLookupResponse {
-    method public void setCallServiceDescriptors(java.util.List<android.telecomm.CallServiceDescriptor>);
-  }
-
-  public abstract class CallServiceProvider extends android.app.Service {
-    ctor protected CallServiceProvider();
-    method public abstract void lookupCallServices(android.telecomm.CallServiceLookupResponse);
-    method public android.os.IBinder onBind(android.content.Intent);
+    field public static final int ALLOWED = 1; // 0x1
+    field public static final int PAYPHONE = 4; // 0x4
+    field public static final int RESTRICTED = 2; // 0x2
+    field public static final int UNKNOWN = 3; // 0x3
   }
 
   public final class CallState extends java.lang.Enum {
@@ -27679,8 +28097,6 @@
     enum_constant public static final android.telecomm.CallState DISCONNECTED;
     enum_constant public static final android.telecomm.CallState NEW;
     enum_constant public static final android.telecomm.CallState ON_HOLD;
-    enum_constant public static final android.telecomm.CallState POST_DIAL;
-    enum_constant public static final android.telecomm.CallState POST_DIAL_WAIT;
     enum_constant public static final android.telecomm.CallState RINGING;
   }
 
@@ -27790,7 +28206,8 @@
 
   public abstract class ConnectionService extends android.app.Service {
     ctor public ConnectionService();
-    method public final void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.RemoteConnection>);
+    method public final void createRemoteIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.RemoteConnection>);
+    method public final void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.RemoteConnection>);
     method public final java.util.Collection<android.telecomm.Connection> getAllConnections();
     method public final void lookupRemoteAccounts(android.net.Uri, android.telecomm.SimpleResponse<android.net.Uri, java.util.List<android.telecomm.PhoneAccount>>);
     method public final void maybeRespondToAccountLookup();
@@ -27798,11 +28215,11 @@
     method protected void onConnectionAdded(android.telecomm.Connection);
     method protected void onConnectionRemoved(android.telecomm.Connection);
     method protected void onCreateConferenceConnection(java.lang.String, android.telecomm.Connection, android.telecomm.Response<java.lang.String, android.telecomm.Connection>);
-    method protected void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.Connection>);
-    method protected void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
+    method protected void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.Connection>);
+    method protected void onCreateOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.Connection>);
   }
 
-  public static abstract interface ConnectionService.OutgoingCallResponse {
+  public static abstract interface ConnectionService.CreateConnectionResponse {
     method public abstract void onCancel(android.telecomm.ConnectionRequest);
     method public abstract void onFailure(android.telecomm.ConnectionRequest, int, java.lang.String);
     method public abstract void onSuccess(android.telecomm.ConnectionRequest, CONNECTION);
@@ -27842,7 +28259,6 @@
     method public java.util.List<java.lang.String> getCannedSmsResponses();
     method public int getCapabilities();
     method public long getConnectTimeMillis();
-    method public android.telecomm.CallServiceDescriptor getCurrentCallServiceDescriptor();
     method public int getDisconnectCauseCode();
     method public java.lang.String getDisconnectCauseMsg();
     method public android.telecomm.GatewayInfo getGatewayInfo();
@@ -27851,21 +28267,34 @@
     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;
   }
 
-  public abstract class InCallService extends android.app.Service {
+  public abstract class InCallService {
     ctor protected InCallService();
-    method protected abstract void addCall(android.telecomm.InCallCall);
-    method protected abstract void bringToForeground(boolean);
-    method protected final android.telecomm.InCallAdapter getAdapter();
-    method protected void onAdapterAttached(android.telecomm.InCallAdapter);
-    method protected abstract void onAudioStateChanged(android.telecomm.CallAudioState);
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method protected abstract void setPostDial(java.lang.String, java.lang.String);
-    method protected abstract void setPostDialWait(java.lang.String, java.lang.String);
-    method protected abstract void updateCall(android.telecomm.InCallCall);
+    method public final android.os.IBinder getBinder();
+    method public android.telecomm.Phone getPhone();
+    method public void onPhoneCreated(android.telecomm.Phone);
+    method public void onPhoneDestroyed(android.telecomm.Phone);
+  }
+
+  public final class Phone {
+    method public final void addListener(android.telecomm.Phone.Listener);
+    method public final android.telecomm.CallAudioState getAudioState();
+    method public final java.util.List<android.telecomm.Call> getCalls();
+    method public final void removeListener(android.telecomm.Phone.Listener);
+    method public final void setAudioRoute(int);
+    method public final void setMuted(boolean);
+  }
+
+  public static abstract class Phone.Listener {
+    ctor public Phone.Listener();
+    method public void onAudioStateChanged(android.telecomm.Phone, android.telecomm.CallAudioState);
+    method public void onBringToForeground(android.telecomm.Phone, boolean);
+    method public void onCallAdded(android.telecomm.Phone, android.telecomm.Call);
+    method public void onCallRemoved(android.telecomm.Phone, android.telecomm.Call);
   }
 
   public class PhoneAccount implements android.os.Parcelable {
@@ -27903,18 +28332,17 @@
     method public void updatePeerDimensions(int, int) throws android.os.RemoteException;
   }
 
-  public class RemoteCallVideoProvider implements android.os.IBinder.DeathRecipient {
-    method public void binderDied();
-    method public void requestCallDataUsage() throws android.os.RemoteException;
-    method public void requestCameraCapabilities() throws android.os.RemoteException;
-    method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile) throws android.os.RemoteException;
-    method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile) throws android.os.RemoteException;
-    method public void setCallVideoClient(android.telecomm.CallVideoClient) throws android.os.RemoteException;
+  public class RemoteCallVideoProvider {
+    method public void requestCallDataUsage();
+    method public void requestCameraCapabilities();
+    method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile);
+    method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile);
+    method public void setCallVideoClient(android.telecomm.CallVideoClient);
     method public void setCamera(java.lang.String) throws android.os.RemoteException;
-    method public void setDeviceOrientation(int) throws android.os.RemoteException;
-    method public void setDisplaySurface(android.view.Surface) throws android.os.RemoteException;
-    method public void setPauseImage(java.lang.String) throws android.os.RemoteException;
-    method public void setPreviewSurface(android.view.Surface) throws android.os.RemoteException;
+    method public void setDeviceOrientation(int);
+    method public void setDisplaySurface(android.view.Surface);
+    method public void setPauseImage(java.lang.String);
+    method public void setPreviewSurface(android.view.Surface);
     method public void setZoom(float) throws android.os.RemoteException;
   }
 
@@ -27981,16 +28409,16 @@
 
   public final class TelecommConstants {
     ctor public TelecommConstants();
-    field public static final java.lang.String ACTION_CALL_SERVICE_PROVIDER;
     field public static final java.lang.String ACTION_CONNECTION_SERVICE;
+    field public static final java.lang.String ACTION_CONNECTION_SERVICE_CONFIGURE = "android.intent.action.CONNECTION_SERVICE_CONFIGURE";
     field public static final java.lang.String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL";
     field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
     field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
     field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecomm.extra.CALL_DISCONNECT_CAUSE";
     field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecomm.extra.CALL_DISCONNECT_MESSAGE";
-    field public static final java.lang.String EXTRA_CALL_SERVICE_DESCRIPTOR = "android.intent.extra.CALL_SERVICE_DESCRIPTOR";
     field public static final java.lang.String EXTRA_CONNECTION_SERVICE = "android.telecomm.extra.CONNECTION_SERVICE";
     field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.intent.extra.INCOMING_CALL_EXTRAS";
+    field public static final java.lang.String EXTRA_PHONE_ACCOUNT = "android.intent.extra.PHONE_ACCOUNT";
     field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.intent.extra.START_CALL_WITH_SPEAKERPHONE";
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.intent.extra.START_CALL_WITH_VIDEO_STATE";
   }
@@ -28429,7 +28857,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();
@@ -28446,19 +28877,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
@@ -28981,6 +29423,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);
@@ -28992,6 +29435,7 @@
     method public java.io.File getFileStreamPath(java.lang.String);
     method public java.io.File getFilesDir();
     method public android.os.Looper getMainLooper();
+    method public java.io.File getNoBackupFilesDir();
     method public java.io.File getObbDir();
     method public java.io.File[] getObbDirs();
     method public java.lang.String getPackageCodePath();
@@ -29128,8 +29572,10 @@
     method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method public android.content.pm.PackageInstaller getInstaller();
     method public java.lang.String getInstallerPackageName(java.lang.String);
     method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
     method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
     method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
     method public java.lang.String getNameForUid(int);
@@ -29147,12 +29593,15 @@
     method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
     method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
     method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
     method public java.lang.String[] getSystemSharedLibraryNames();
     method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public boolean hasSystemFeature(java.lang.String);
     method public boolean isSafeMode();
+    method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
+    method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
     method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
     method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
     method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
@@ -34042,6 +34491,7 @@
     method protected final int getForcedWindowFlags();
     method public abstract android.view.LayoutInflater getLayoutInflater();
     method protected final int getLocalFeatures();
+    method public android.media.session.MediaController getMediaController();
     method public abstract int getNavigationBarColor();
     method public android.transition.Transition getSharedElementEnterTransition();
     method public android.transition.Transition getSharedElementExitTransition();
@@ -34098,6 +34548,7 @@
     method public void setLayout(int, int);
     method public void setLocalFocus(boolean, boolean);
     method public void setLogo(int);
+    method public void setMediaController(android.media.session.MediaController);
     method public abstract void setNavigationBarColor(int);
     method public void setSharedElementEnterTransition(android.transition.Transition);
     method public void setSharedElementExitTransition(android.transition.Transition);
@@ -35769,14 +36220,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 {
@@ -36100,7 +36565,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
@@ -36764,8 +37230,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();
@@ -36773,7 +37247,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 4e91361..800b925 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -25,7 +25,6 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSessionInfo;
 import android.media.session.PlaybackState;
-import android.media.session.RouteInfo;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -181,11 +180,6 @@
         }
 
         @Override
-        public void onRouteChanged(RouteInfo route) {
-            System.out.println("onRouteChanged " + route);
-        }
-
-        @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             System.out.println("onPlaybackStateChanged " + state);
         }
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 3d0eec4..b7f1ff9 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -50,6 +50,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.content.PackageHelper;
@@ -923,33 +924,13 @@
                     return;
                 }
             } else if (opt.equals("--abi")) {
-                abi = nextOptionData();
-                if (abi == null) {
-                    System.err.println("Error: must supply argument for --abi");
-                    return;
-                }
+                abi = checkAbiArgument(nextOptionData());
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
             }
         }
 
-        if (abi != null) {
-            final String[] supportedAbis = Build.SUPPORTED_ABIS;
-            boolean matched = false;
-            for (String supportedAbi : supportedAbis) {
-                if (supportedAbi.equals(abi)) {
-                    matched = true;
-                    break;
-                }
-            }
-
-            if (!matched) {
-                System.err.println("Error: abi " + abi + " not supported on this device.");
-                return;
-            }
-        }
-
         final Uri verificationURI;
         final Uri originatingURI;
         final Uri referrerURI;
@@ -1017,7 +998,7 @@
 
         final InstallSessionParams params = new InstallSessionParams();
         params.installFlags = PackageManager.INSTALL_ALL_USERS;
-        params.fullInstall = true;
+        params.mode = InstallSessionParams.MODE_FULL_INSTALL;
         params.progressMax = -1;
 
         String opt;
@@ -1040,10 +1021,12 @@
             } else if (opt.equals("-d")) {
                 params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
             } else if (opt.equals("-p")) {
-                params.fullInstall = false;
+                params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
             } else if (opt.equals("-S")) {
                 params.deltaSize = Long.parseLong(nextOptionData());
                 params.progressMax = (int) params.deltaSize;
+            } else if (opt.equals("--abi")) {
+                params.abiOverride = checkAbiArgument(nextOptionData());
             } else {
                 throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -1684,6 +1667,21 @@
         }
     }
 
+    private static String checkAbiArgument(String abi) {
+        if (TextUtils.isEmpty(abi)) {
+            throw new IllegalArgumentException("Missing ABI argument");
+        }
+
+        final String[] supportedAbis = Build.SUPPORTED_ABIS;
+        for (String supportedAbi : supportedAbis) {
+            if (supportedAbi.equals(abi)) {
+                return abi;
+            }
+        }
+
+        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
+    }
+
     private String nextOption() {
         if (mNextArg >= mArgs.length) {
             return null;
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/app/Activity.java b/core/java/android/app/Activity.java
index f1a2576..cac646d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -23,6 +23,7 @@
 import android.util.ArrayMap;
 import android.util.SuperNotCalledException;
 import android.widget.Toolbar;
+
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.WindowDecorActionBar;
 import com.android.internal.app.ToolbarActionBar;
@@ -51,6 +52,8 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -109,14 +112,14 @@
  * or embedded inside of another activity (using {@link ActivityGroup}).
  *
  * There are two methods almost all subclasses of Activity will implement:
- * 
+ *
  * <ul>
  *     <li> {@link #onCreate} is where you initialize your activity.  Most
  *     importantly, here you will usually call {@link #setContentView(int)}
  *     with a layout resource defining your UI, and using {@link #findViewById}
  *     to retrieve the widgets in that UI that you need to interact with
  *     programmatically.
- * 
+ *
  *     <li> {@link #onPause} is where you deal with the user leaving your
  *     activity.  Most importantly, any changes made by the user should at this
  *     point be committed (usually to the
@@ -127,7 +130,7 @@
  * activity classes must have a corresponding
  * {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
  * declaration in their package's <code>AndroidManifest.xml</code>.</p>
- * 
+ *
  * <p>Topics covered here:
  * <ol>
  * <li><a href="#Fragments">Fragments</a>
@@ -170,14 +173,14 @@
  * and becomes the running activity -- the previous activity always remains
  * below it in the stack, and will not come to the foreground again until
  * the new activity exits.</p>
- * 
+ *
  * <p>An activity has essentially four states:</p>
  * <ul>
  *     <li> If an activity in the foreground of the screen (at the top of
  *         the stack),
  *         it is <em>active</em> or  <em>running</em>. </li>
  *     <li>If an activity has lost focus but is still visible (that is, a new non-full-sized
- *         or transparent activity has focus on top of your activity), it 
+ *         or transparent activity has focus on top of your activity), it
  *         is <em>paused</em>. A paused activity is completely alive (it
  *         maintains all state and member information and remains attached to
  *         the window manager), but can be killed by the system in extreme
@@ -197,13 +200,13 @@
  * The square rectangles represent callback methods you can implement to
  * perform operations when the Activity moves between states.  The colored
  * ovals are major states the Activity can be in.</p>
- * 
+ *
  * <p><img src="../../../images/activity_lifecycle.png"
  *      alt="State diagram for an Android Activity Lifecycle." border="0" /></p>
- * 
+ *
  * <p>There are three key loops you may be interested in monitoring within your
  * activity:
- * 
+ *
  * <ul>
  * <li>The <b>entire lifetime</b> of an activity happens between the first call
  * to {@link android.app.Activity#onCreate} through to a single final call
@@ -212,7 +215,7 @@
  * onDestroy().  For example, if it has a thread running in the background
  * to download data from the network, it may create that thread in onCreate()
  * and then stop the thread in onDestroy().
- * 
+ *
  * <li>The <b>visible lifetime</b> of an activity happens between a call to
  * {@link android.app.Activity#onStart} until a corresponding call to
  * {@link android.app.Activity#onStop}.  During this time the user can see the
@@ -224,7 +227,7 @@
  * longer sees what you are displaying.  The onStart() and onStop() methods
  * can be called multiple times, as the activity becomes visible and hidden
  * to the user.
- * 
+ *
  * <li>The <b>foreground lifetime</b> of an activity happens between a call to
  * {@link android.app.Activity#onResume} until a corresponding call to
  * {@link android.app.Activity#onPause}.  During this time the activity is
@@ -234,7 +237,7 @@
  * intent is delivered -- so the code in these methods should be fairly
  * lightweight.
  * </ul>
- * 
+ *
  * <p>The entire lifecycle of an activity is defined by the following
  * Activity methods.  All of these are hooks that you can override
  * to do appropriate work when the activity changes state.  All
@@ -250,7 +253,7 @@
  *     protected void onCreate(Bundle savedInstanceState);
  *
  *     protected void onStart();
- *     
+ *
  *     protected void onRestart();
  *
  *     protected void onResume();
@@ -366,7 +369,7 @@
  * {@link #onSaveInstanceState(Bundle)} is called before placing the activity
  * in such a background state, allowing you to save away any dynamic instance
  * state in your activity into the given Bundle, to be later received in
- * {@link #onCreate} if the activity needs to be re-created.  
+ * {@link #onCreate} if the activity needs to be re-created.
  * See the <a href="#ProcessLifecycle">Process Lifecycle</a>
  * section for more information on how the lifecycle of a process is tied
  * to the activities it is hosting.  Note that it is important to save
@@ -390,14 +393,14 @@
  *
  * <a name="ConfigurationChanges"></a>
  * <h3>Configuration Changes</h3>
- * 
+ *
  * <p>If the configuration of the device (as defined by the
  * {@link Configuration Resources.Configuration} class) changes,
  * then anything displaying a user interface will need to update to match that
  * configuration.  Because Activity is the primary mechanism for interacting
  * with the user, it includes special support for handling configuration
  * changes.</p>
- * 
+ *
  * <p>Unless you specify otherwise, a configuration change (such as a change
  * in screen orientation, language, input devices, etc) will cause your
  * current activity to be <em>destroyed</em>, going through the normal activity
@@ -407,7 +410,7 @@
  * called in that instance then a new instance of the activity will be
  * created, with whatever savedInstanceState the previous instance had generated
  * from {@link #onSaveInstanceState}.</p>
- * 
+ *
  * <p>This is done because any application resource,
  * including layout files, can change based on any configuration value.  Thus
  * the only safe way to handle a configuration change is to re-retrieve all
@@ -415,7 +418,7 @@
  * must already know how to save their state and re-create themselves from
  * that state, this is a convenient way to have an activity restart itself
  * with a new configuration.</p>
- * 
+ *
  * <p>In some special cases, you may want to bypass restarting of your
  * activity based on one or more types of configuration changes.  This is
  * done with the {@link android.R.attr#configChanges android:configChanges}
@@ -425,7 +428,7 @@
  * a configuration change involves any that you do not handle, however, the
  * activity will still be restarted and {@link #onConfigurationChanged}
  * will not be called.</p>
- * 
+ *
  * <a name="StartingActivities"></a>
  * <h3>Starting Activities and Getting Results</h3>
  *
@@ -440,10 +443,10 @@
  * ends.  For example, you may start an activity that lets the user pick
  * a person in a list of contacts; when it ends, it returns the person
  * that was selected.  To do this, you call the
- * {@link android.app.Activity#startActivityForResult(Intent, int)} 
- * version with a second integer parameter identifying the call.  The result 
+ * {@link android.app.Activity#startActivityForResult(Intent, int)}
+ * version with a second integer parameter identifying the call.  The result
  * will come back through your {@link android.app.Activity#onActivityResult}
- * method.</p> 
+ * method.</p>
  *
  * <p>When an activity exits, it can call
  * {@link android.app.Activity#setResult(int)}
@@ -570,17 +573,17 @@
  *
  *     protected void onPause() {
  *         super.onPause();
- * 
+ *
  *         SharedPreferences.Editor ed = mPrefs.edit();
  *         ed.putInt("view_mode", mCurViewMode);
  *         ed.commit();
  *     }
  * }
  * </pre>
- * 
+ *
  * <a name="Permissions"></a>
  * <h3>Permissions</h3>
- * 
+ *
  * <p>The ability to start a particular Activity can be enforced when it is
  * declared in its
  * manifest's {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
@@ -601,10 +604,10 @@
  *
  * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
  * document for more information on permissions and security in general.
- * 
+ *
  * <a name="ProcessLifecycle"></a>
  * <h3>Process Lifecycle</h3>
- * 
+ *
  * <p>The Android system attempts to keep application process around for as
  * long as possible, but eventually will need to remove old processes when
  * memory runs low.  As described in <a href="#ActivityLifecycle">Activity
@@ -614,7 +617,7 @@
  * listed here in order of importance.  The system will kill less important
  * processes (the last ones) before it resorts to killing more important
  * processes (the first ones).
- * 
+ *
  * <ol>
  * <li> <p>The <b>foreground activity</b> (the activity at the top of the screen
  * that the user is currently interacting with) is considered the most important.
@@ -642,7 +645,7 @@
  * context of an activity BroadcastReceiver or Service to ensure that the system
  * knows it needs to keep your process around.
  * </ol>
- * 
+ *
  * <p>Sometimes an Activity may need to do a long-running operation that exists
  * independently of the activity lifecycle itself.  An example may be a camera
  * application that allows you to upload a picture to a web site.  The upload
@@ -720,7 +723,7 @@
         VoiceInteractor voiceInteractor;
     }
     /* package */ NonConfigurationInstances mLastNonConfigurationInstances;
-    
+
     private Window mWindow;
 
     private WindowManager mWindowManager;
@@ -764,7 +767,7 @@
     private final ArrayList<ManagedCursor> mManagedCursors =
         new ArrayList<ManagedCursor>();
 
-    // protected by synchronized (this) 
+    // protected by synchronized (this)
     int mResultCode = RESULT_CANCELED;
     Intent mResultData = null;
 
@@ -775,7 +778,7 @@
 
     private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE;
     private SpannableStringBuilder mDefaultKeySsb = null;
-    
+
     protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
 
     @SuppressWarnings("unused")
@@ -793,16 +796,16 @@
         return mIntent;
     }
 
-    /** 
-     * Change the intent returned by {@link #getIntent}.  This holds a 
-     * reference to the given intent; it does not copy it.  Often used in 
-     * conjunction with {@link #onNewIntent}. 
-     *  
-     * @param newIntent The new Intent object to return from getIntent 
-     * 
+    /**
+     * Change the intent returned by {@link #getIntent}.  This holds a
+     * reference to the given intent; it does not copy it.  Often used in
+     * conjunction with {@link #onNewIntent}.
+     *
+     * @param newIntent The new Intent object to return from getIntent
+     *
      * @see #getIntent
      * @see #onNewIntent
-     */ 
+     */
     public void setIntent(Intent newIntent) {
         mIntent = newIntent;
     }
@@ -816,7 +819,7 @@
     public final boolean isChild() {
         return mParent != null;
     }
-    
+
     /** Return the parent activity if this view is an embedded child. */
     public final Activity getParent() {
         return mParent;
@@ -831,7 +834,7 @@
      * Retrieve the current {@link android.view.Window} for the activity.
      * This can be used to directly access parts of the Window API that
      * are not available through Activity/Screen.
-     * 
+     *
      * @return Window The current window, or null if the activity is not
      *         visual.
      */
@@ -850,7 +853,7 @@
         mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
         return mLoaderManager;
     }
-    
+
     LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
         if (mAllLoaderManagers == null) {
             mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
@@ -866,13 +869,13 @@
         }
         return lm;
     }
-    
+
     /**
      * Calls {@link android.view.Window#getCurrentFocus} on the
      * Window of this Activity to return the currently focused view.
-     * 
+     *
      * @return View The current View with focus or null.
-     * 
+     *
      * @see #getWindow
      * @see android.view.Window#getCurrentFocus
      */
@@ -888,20 +891,20 @@
      * with widgets in the UI, calling
      * {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve
      * cursors for data being displayed, etc.
-     * 
+     *
      * <p>You can call {@link #finish} from within this function, in
      * which case onDestroy() will be immediately called without any of the rest
      * of the activity lifecycle ({@link #onStart}, {@link #onResume},
      * {@link #onPause}, etc) executing.
-     * 
+     *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @param savedInstanceState If the activity is being re-initialized after
      *     previously being shut down then this Bundle contains the data it most
      *     recently supplied in {@link #onSaveInstanceState}.  <b><i>Note: Otherwise it is null.</i></b>
-     * 
+     *
      * @see #onStart
      * @see #onSaveInstanceState
      * @see #onRestoreInstanceState
@@ -996,12 +999,12 @@
      * decide whether to use your default implementation.  The default
      * implementation of this method performs a restore of any view state that
      * had previously been frozen by {@link #onSaveInstanceState}.
-     * 
+     *
      * <p>This method is called between {@link #onStart} and
      * {@link #onPostCreate}.
-     * 
+     *
      * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
-     * 
+     *
      * @see #onCreate
      * @see #onPostCreate
      * @see #onResume
@@ -1098,11 +1101,11 @@
      * and {@link #onRestoreInstanceState} have been called).  Applications will
      * generally not implement this method; it is intended for system
      * classes to do final initialization after application code has run.
-     * 
+     *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @param savedInstanceState If the activity is being re-initialized after
      *     previously being shut down then this Bundle contains the data it most
      *     recently supplied in {@link #onSaveInstanceState}.  <b><i>Note: Otherwise it is null.</i></b>
@@ -1133,14 +1136,14 @@
     }
 
     /**
-     * Called after {@link #onCreate} &mdash; or after {@link #onRestart} when  
-     * the activity had been stopped, but is now again being displayed to the 
+     * Called after {@link #onCreate} &mdash; or after {@link #onRestart} when
+     * the activity had been stopped, but is now again being displayed to the
      * user.  It will be followed by {@link #onResume}.
      *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @see #onCreate
      * @see #onStop
      * @see #onResume
@@ -1148,7 +1151,7 @@
     protected void onStart() {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
         mCalled = true;
-        
+
         if (!mLoadersStarted) {
             mLoadersStarted = true;
             if (mLoaderManager != null) {
@@ -1173,11 +1176,11 @@
      * this is usually the place
      * where the cursor should be requeried (because you had deactivated it in
      * {@link #onStop}.
-     * 
+     *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @see #onStop
      * @see #onStart
      * @see #onResume
@@ -1200,7 +1203,7 @@
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @see #onRestoreInstanceState
      * @see #onRestart
      * @see #onPostResume
@@ -1218,11 +1221,11 @@
      * been called). Applications will generally not implement this method;
      * it is intended for system classes to do final setup after application
      * resume code has run.
-     * 
+     *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @see #onResume
      */
     protected void onPostResume() {
@@ -1256,19 +1259,19 @@
      * activity is re-launched while at the top of the activity stack instead
      * of a new instance of the activity being started, onNewIntent() will be
      * called on the existing instance with the Intent that was used to
-     * re-launch it. 
-     *  
-     * <p>An activity will always be paused before receiving a new intent, so 
-     * you can count on {@link #onResume} being called after this method. 
-     * 
-     * <p>Note that {@link #getIntent} still returns the original Intent.  You 
-     * can use {@link #setIntent} to update it to this new Intent. 
-     * 
-     * @param intent The new intent that was started for the activity. 
-     *  
+     * re-launch it.
+     *
+     * <p>An activity will always be paused before receiving a new intent, so
+     * you can count on {@link #onResume} being called after this method.
+     *
+     * <p>Note that {@link #getIntent} still returns the original Intent.  You
+     * can use {@link #setIntent} to update it to this new Intent.
+     *
+     * @param intent The new intent that was started for the activity.
+     *
      * @see #getIntent
-     * @see #setIntent 
-     * @see #onResume 
+     * @see #setIntent
+     * @see #onResume
      */
     protected void onNewIntent(Intent intent) {
     }
@@ -1342,9 +1345,9 @@
      *
      * <p>If called, this method will occur before {@link #onStop}.  There are
      * no guarantees about whether it will occur before or after {@link #onPause}.
-     * 
+     *
      * @param outState Bundle in which to place your saved state.
-     * 
+     *
      * @see #onCreate
      * @see #onRestoreInstanceState
      * @see #onPause
@@ -1429,23 +1432,23 @@
      * noticeable amount of CPU in order to make the switch to the next activity
      * as fast as possible, or to close resources that are exclusive access
      * such as the camera.
-     * 
+     *
      * <p>In situations where the system needs more memory it may kill paused
      * processes to reclaim resources.  Because of this, you should be sure
      * that all of your state is saved by the time you return from
      * this function.  In general {@link #onSaveInstanceState} is used to save
      * per-instance state in the activity and this method is used to store
      * global persistent data (in content providers, files, etc.)
-     * 
+     *
      * <p>After receiving this call you will usually receive a following call
      * to {@link #onStop} (after the next activity has been resumed and
      * displayed), however in some cases there will be a direct call back to
      * {@link #onResume} without going through the stopped state.
-     * 
+     *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @see #onResume
      * @see #onSaveInstanceState
      * @see #onStop
@@ -1464,32 +1467,32 @@
      * brought to the foreground, {@link #onUserLeaveHint} will not be called on
      * the activity being interrupted.  In cases when it is invoked, this method
      * is called right before the activity's {@link #onPause} callback.
-     * 
+     *
      * <p>This callback and {@link #onUserInteraction} are intended to help
      * activities manage status bar notifications intelligently; specifically,
      * for helping activities determine the proper time to cancel a notfication.
-     * 
+     *
      * @see #onUserInteraction()
      */
     protected void onUserLeaveHint() {
     }
-    
+
     /**
      * Generate a new thumbnail for this activity.  This method is called before
      * pausing the activity, and should draw into <var>outBitmap</var> the
      * imagery for the desired thumbnail in the dimensions of that bitmap.  It
      * can use the given <var>canvas</var>, which is configured to draw into the
      * bitmap, for rendering if desired.
-     * 
+     *
      * <p>The default implementation returns fails and does not draw a thumbnail;
      * this will result in the platform creating its own thumbnail if needed.
-     * 
+     *
      * @param outBitmap The bitmap to contain the thumbnail.
      * @param canvas Can be used to render into the bitmap.
-     * 
+     *
      * @return Return true if you have drawn into the bitmap; otherwise after
      *         you return it will be filled with a default thumbnail.
-     * 
+     *
      * @see #onCreateDescription
      * @see #onSaveInstanceState
      * @see #onPause
@@ -1502,15 +1505,15 @@
      * Generate a new description for this activity.  This method is called
      * before pausing the activity and can, if desired, return some textual
      * description of its current state to be displayed to the user.
-     * 
+     *
      * <p>The default implementation returns null, which will cause you to
      * inherit the description from the previous activity.  If all activities
      * return null, generally the label of the top activity will be used as the
      * description.
-     * 
+     *
      * @return A description of what the user is doing.  It should be short and
      *         sweet (only a few words).
-     * 
+     *
      * @see #onCreateThumbnail
      * @see #onSaveInstanceState
      * @see #onPause
@@ -1538,15 +1541,15 @@
      * Called when you are no longer visible to the user.  You will next
      * receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
      * depending on later user activity.
-     * 
+     *
      * <p>Note that this method may never be called, in low memory situations
      * where the system does not have enough memory to keep your activity's
      * process running after its {@link #onPause} method is called.
-     * 
+     *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @see #onRestart
      * @see #onResume
      * @see #onSaveInstanceState
@@ -1567,7 +1570,7 @@
      * {@link #finish} on it, or because the system is temporarily destroying
      * this instance of the activity to save space.  You can distinguish
      * between these two scenarios with the {@link #isFinishing} method.
-     * 
+     *
      * <p><em>Note: do not count on this method being called as a place for
      * saving data! For example, if an activity is editing data in a content
      * provider, those edits should be committed in either {@link #onPause} or
@@ -1579,11 +1582,11 @@
      * calling this method (or any others) in it, so it should not be used to
      * do things that are intended to remain around after the process goes
      * away.
-     * 
+     *
      * <p><em>Derived classes must call through to the super class's
      * implementation of this method.  If they do not, an exception will be
      * thrown.</em></p>
-     * 
+     *
      * @see #onPause
      * @see #onStop
      * @see #finish
@@ -1657,11 +1660,11 @@
      * by that attribute, then instead of reporting it the system will stop
      * and restart the activity (to have it launched with the new
      * configuration).
-     * 
+     *
      * <p>At the time that this function has been called, your Resources
      * object will have been updated to return resource values matching the
      * new configuration.
-     * 
+     *
      * @param newConfig The new device configuration.
      */
     public void onConfigurationChanged(Configuration newConfig) {
@@ -1681,7 +1684,7 @@
             mActionBar.onConfigurationChanged(newConfig);
         }
     }
-    
+
     /**
      * If this activity is being destroyed because it can not handle a
      * configuration parameter being changed (and thus its
@@ -1691,7 +1694,7 @@
      * destroyed.  Note that there is no guarantee that these will be
      * accurate (other changes could have happened at any time), so you should
      * only use this as an optimization hint.
-     * 
+     *
      * @return Returns a bit field of the configuration parameters that are
      * changing, as defined by the {@link android.content.res.Configuration}
      * class.
@@ -1699,21 +1702,21 @@
     public int getChangingConfigurations() {
         return mConfigChangeFlags;
     }
-    
+
     /**
      * Retrieve the non-configuration instance data that was previously
      * returned by {@link #onRetainNonConfigurationInstance()}.  This will
      * be available from the initial {@link #onCreate} and
      * {@link #onStart} calls to the new instance, allowing you to extract
      * any useful dynamic state from the previous instance.
-     * 
+     *
      * <p>Note that the data you retrieve here should <em>only</em> be used
      * as an optimization for handling configuration changes.  You should always
      * be able to handle getting a null pointer back, and an activity must
      * still be able to restore itself to its previous state (through the
      * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
      * function returns null.
-     * 
+     *
      * @return Returns the object previously returned by
      * {@link #onRetainNonConfigurationInstance()}.
      *
@@ -1727,7 +1730,7 @@
         return mLastNonConfigurationInstances != null
                 ? mLastNonConfigurationInstances.activity : null;
     }
-    
+
     /**
      * Called by the system, as part of destroying an
      * activity due to a configuration change, when it is known that a new
@@ -1736,7 +1739,7 @@
      * itself, which can later be retrieved by calling
      * {@link #getLastNonConfigurationInstance()} in the new activity
      * instance.
-     * 
+     *
      * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
      * or later, consider instead using a {@link Fragment} with
      * {@link Fragment#setRetainInstance(boolean)
@@ -1756,14 +1759,14 @@
      * the {@link #getLastNonConfigurationInstance()} method of the following
      * activity instance as described there.
      * </ul>
-     * 
+     *
      * <p>These guarantees are designed so that an activity can use this API
      * to propagate extensive state from the old to new activity instance, from
      * loaded bitmaps, to network connections, to evenly actively running
      * threads.  Note that you should <em>not</em> propagate any data that
      * may change based on the configuration, including any data loaded from
      * resources such as strings, layouts, or drawables.
-     * 
+     *
      * <p>The guarantee of no message handling during the switch to the next
      * activity simplifies use with active objects.  For example if your retained
      * state is an {@link android.os.AsyncTask} you are guaranteed that its
@@ -1783,21 +1786,21 @@
     public Object onRetainNonConfigurationInstance() {
         return null;
     }
-    
+
     /**
      * Retrieve the non-configuration instance data that was previously
      * returned by {@link #onRetainNonConfigurationChildInstances()}.  This will
      * be available from the initial {@link #onCreate} and
      * {@link #onStart} calls to the new instance, allowing you to extract
      * any useful dynamic state from the previous instance.
-     * 
+     *
      * <p>Note that the data you retrieve here should <em>only</em> be used
      * as an optimization for handling configuration changes.  You should always
      * be able to handle getting a null pointer back, and an activity must
      * still be able to restore itself to its previous state (through the
      * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
      * function returns null.
-     * 
+     *
      * @return Returns the object previously returned by
      * {@link #onRetainNonConfigurationChildInstances()}
      */
@@ -1806,7 +1809,7 @@
         return mLastNonConfigurationInstances != null
                 ? mLastNonConfigurationInstances.children : null;
     }
-    
+
     /**
      * This method is similar to {@link #onRetainNonConfigurationInstance()} except that
      * it should return either a mapping from  child activity id strings to arbitrary objects,
@@ -1818,7 +1821,7 @@
     HashMap<String,Object> onRetainNonConfigurationChildInstances() {
         return null;
     }
-    
+
     NonConfigurationInstances retainNonConfigurationInstances() {
         Object activity = onRetainNonConfigurationInstance();
         HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
@@ -1846,7 +1849,7 @@
                 && mVoiceInteractor == null) {
             return null;
         }
-        
+
         NonConfigurationInstances nci = new NonConfigurationInstances();
         nci.activity = activity;
         nci.children = children;
@@ -1886,7 +1889,7 @@
             }
         }
     }
-    
+
     /**
      * Called when a Fragment is being attached to this activity, immediately
      * after the call to its {@link Fragment#onAttach Fragment.onAttach()}
@@ -1894,14 +1897,14 @@
      */
     public void onAttachFragment(Fragment fragment) {
     }
-    
+
     /**
      * Wrapper around
      * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
      * that gives the resulting {@link Cursor} to call
      * {@link #startManagingCursor} so that the activity will manage its
      * lifecycle for you.
-     * 
+     *
      * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
      * or later, consider instead using {@link LoaderManager} instead, available
      * via {@link #getLoaderManager()}.</em>
@@ -1911,14 +1914,14 @@
      * you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
      * not</em> automatically close the cursor and, in that case, you must call
      * {@link Cursor#close()}.</p>
-     * 
+     *
      * @param uri The URI of the content provider to query.
      * @param projection List of columns to return.
      * @param selection SQL WHERE clause.
      * @param sortOrder SQL ORDER BY clause.
-     * 
+     *
      * @return The Cursor that was returned by query().
-     * 
+     *
      * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
      * @see #startManagingCursor
      * @hide
@@ -1941,7 +1944,7 @@
      * that gives the resulting {@link Cursor} to call
      * {@link #startManagingCursor} so that the activity will manage its
      * lifecycle for you.
-     * 
+     *
      * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
      * or later, consider instead using {@link LoaderManager} instead, available
      * via {@link #getLoaderManager()}.</em>
@@ -1951,15 +1954,15 @@
      * you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
      * not</em> automatically close the cursor and, in that case, you must call
      * {@link Cursor#close()}.</p>
-     * 
+     *
      * @param uri The URI of the content provider to query.
      * @param projection List of columns to return.
      * @param selection SQL WHERE clause.
      * @param selectionArgs The arguments to selection, if any ?s are pesent
      * @param sortOrder SQL ORDER BY clause.
-     * 
+     *
      * @return The Cursor that was returned by query().
-     * 
+     *
      * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
      * @see #startManagingCursor
      *
@@ -1982,7 +1985,7 @@
      * {@link Cursor#deactivate} on the given Cursor, and when it is later restarted
      * it will call {@link Cursor#requery} for you.  When the activity is
      * destroyed, all managed Cursors will be closed automatically.
-     * 
+     *
      * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
      * or later, consider instead using {@link LoaderManager} instead, available
      * via {@link #getLoaderManager()}.</em>
@@ -1992,9 +1995,9 @@
      * However, if you call {@link #stopManagingCursor} on a cursor from a managed query, the system
      * <em>will not</em> automatically close the cursor and, in that case, you must call
      * {@link Cursor#close()}.</p>
-     * 
+     *
      * @param c The Cursor to be managed.
-     * 
+     *
      * @see #managedQuery(android.net.Uri , String[], String, String[], String)
      * @see #stopManagingCursor
      *
@@ -2013,13 +2016,13 @@
      * Given a Cursor that was previously given to
      * {@link #startManagingCursor}, stop the activity's management of that
      * cursor.
-     * 
+     *
      * <p><strong>Warning:</strong> After calling this method on a cursor from a managed query,
-     * the system <em>will not</em> automatically close the cursor and you must call 
+     * the system <em>will not</em> automatically close the cursor and you must call
      * {@link Cursor#close()}.</p>
-     * 
+     *
      * @param c The Cursor that was being managed.
-     * 
+     *
      * @see #startManagingCursor
      *
      * @deprecated Use the new {@link android.content.CursorLoader} class with
@@ -2058,7 +2061,7 @@
     public View findViewById(int id) {
         return getWindow().findViewById(id);
     }
-    
+
     /**
      * Retrieve a reference to this activity's ActionBar.
      *
@@ -2094,7 +2097,7 @@
         mActionBar = new ToolbarActionBar(toolbar, getTitle(), this);
         mActionBar.invalidateOptionsMenu();
     }
-    
+
     /**
      * Creates a new ActionBar, locates the inflated ActionBarView,
      * initializes the ActionBar with the view, and sets mActionBar.
@@ -2116,13 +2119,13 @@
         mWindow.setDefaultIcon(mActivityInfo.getIconResource());
         mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
     }
-    
+
     /**
      * Set the activity content from a layout resource.  The resource will be
      * inflated, adding all top-level views to the activity.
      *
      * @param layoutResID Resource ID to be inflated.
-     * 
+     *
      * @see #setContentView(android.view.View)
      * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
      */
@@ -2140,7 +2143,7 @@
      * your own layout parameters, invoke
      * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
      * instead.
-     * 
+     *
      * @param view The desired content to display.
      *
      * @see #setContentView(int)
@@ -2155,7 +2158,7 @@
      * Set the activity content to an explicit view.  This view is placed
      * directly into the activity's view hierarchy.  It can itself be a complex
      * view hierarchy.
-     * 
+     *
      * @param view The desired content to display.
      * @param params Layout parameters for the view.
      *
@@ -2170,7 +2173,7 @@
     /**
      * Add an additional content view to the activity.  Added after any existing
      * ones in the activity -- existing views are NOT removed.
-     * 
+     *
      * @param view The desired content to display.
      * @param params Layout parameters for the view.
      */
@@ -2235,23 +2238,23 @@
     /**
      * Use with {@link #setDefaultKeyMode} to turn off default handling of
      * keys.
-     * 
+     *
      * @see #setDefaultKeyMode
      */
     static public final int DEFAULT_KEYS_DISABLE = 0;
     /**
      * Use with {@link #setDefaultKeyMode} to launch the dialer during default
      * key handling.
-     * 
+     *
      * @see #setDefaultKeyMode
      */
     static public final int DEFAULT_KEYS_DIALER = 1;
     /**
      * Use with {@link #setDefaultKeyMode} to execute a menu shortcut in
      * default key handling.
-     * 
+     *
      * <p>That is, the user does not need to hold down the menu key to execute menu shortcuts.
-     * 
+     *
      * @see #setDefaultKeyMode
      */
     static public final int DEFAULT_KEYS_SHORTCUT = 2;
@@ -2259,9 +2262,9 @@
      * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
      * will start an application-defined search.  (If the application or activity does not
      * actually define a search, the the keys will be ignored.)
-     * 
+     *
      * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
-     * 
+     *
      * @see #setDefaultKeyMode
      */
     static public final int DEFAULT_KEYS_SEARCH_LOCAL = 3;
@@ -2270,9 +2273,9 @@
      * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
      * will start a global search (typically web search, but some platforms may define alternate
      * methods for global search)
-     * 
+     *
      * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
-     * 
+     *
      * @see #setDefaultKeyMode
      */
     static public final int DEFAULT_KEYS_SEARCH_GLOBAL = 4;
@@ -2284,16 +2287,16 @@
      * floor. Other modes allow you to launch the dialer
      * ({@link #DEFAULT_KEYS_DIALER}), execute a shortcut in your options
      * menu without requiring the menu key be held down
-     * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL} 
+     * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL}
      * and {@link #DEFAULT_KEYS_SEARCH_GLOBAL}).
-     * 
+     *
      * <p>Note that the mode selected here does not impact the default
      * handling of system keys, such as the "back" and "menu" keys, and your
      * activity and its views always get a first chance to receive and handle
      * all application keys.
-     * 
+     *
      * @param mode The desired default key mode constant.
-     * 
+     *
      * @see #DEFAULT_KEYS_DISABLE
      * @see #DEFAULT_KEYS_DIALER
      * @see #DEFAULT_KEYS_SHORTCUT
@@ -2303,7 +2306,7 @@
      */
     public final void setDefaultKeyMode(@DefaultKeyMode int mode) {
         mDefaultKeyMode = mode;
-        
+
         // Some modes use a SpannableStringBuilder to track & dispatch input events
         // This list must remain in sync with the switch in onKeyDown()
         switch (mode) {
@@ -2324,10 +2327,10 @@
 
     /**
      * Called when a key was pressed down and not handled by any of the views
-     * inside of the activity. So, for example, key presses while the cursor 
+     * inside of the activity. So, for example, key presses while the cursor
      * is inside a TextView will not trigger the event (unless it is a navigation
      * to another object) because TextView handles its own key presses.
-     * 
+     *
      * <p>If the focused view didn't want this event, this method is called.
      *
      * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK}
@@ -2338,12 +2341,12 @@
      * will be performed; for earlier applications, it will perform the
      * action immediately in on-down, as those versions of the platform
      * behaved.
-     * 
+     *
      * <p>Other additional default key handling may be performed
      * if configured with {@link #setDefaultKeyMode}.
-     * 
+     *
      * @return Return <code>true</code> to prevent this event from being propagated
-     * further, or <code>false</code> to indicate that you have not handled 
+     * further, or <code>false</code> to indicate that you have not handled
      * this event and it should continue to be propagated.
      * @see #onKeyUp
      * @see android.view.KeyEvent
@@ -2358,11 +2361,11 @@
             }
             return true;
         }
-        
+
         if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
             return false;
         } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
-            if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, 
+            if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
                     keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
                 return true;
             }
@@ -2382,12 +2385,12 @@
 
                     final String str = mDefaultKeySsb.toString();
                     clearSpannable = true;
-                    
+
                     switch (mDefaultKeyMode) {
                     case DEFAULT_KEYS_DIALER:
                         Intent intent = new Intent(Intent.ACTION_DIAL,  Uri.parse("tel:" + str));
                         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                        startActivity(intent);    
+                        startActivity(intent);
                         break;
                     case DEFAULT_KEYS_SEARCH_LOCAL:
                         startSearch(str, false, null, false);
@@ -2418,16 +2421,16 @@
 
     /**
      * Called when a key was released and not handled by any of the views
-     * inside of the activity. So, for example, key presses while the cursor 
+     * inside of the activity. So, for example, key presses while the cursor
      * is inside a TextView will not trigger the event (unless it is a navigation
      * to another object) because TextView handles its own key presses.
-     * 
+     *
      * <p>The default implementation handles KEYCODE_BACK to stop the activity
      * and go back.
-     * 
+     *
      * @return Return <code>true</code> to prevent this event from being propagated
-     * further, or <code>false</code> to indicate that you have not handled 
-     * this event and it should continue to be propagated. 
+     * further, or <code>false</code> to indicate that you have not handled
+     * this event and it should continue to be propagated.
      * @see #onKeyDown
      * @see KeyEvent
      */
@@ -2451,7 +2454,7 @@
     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
         return false;
     }
-    
+
     /**
      * Called when the activity has detected the user's press of the back
      * key.  The default implementation simply finishes the current activity,
@@ -2485,9 +2488,9 @@
      * Called when a touch screen event was not handled by any of the views
      * under it.  This is most useful to process touch events that happen
      * outside of your window bounds, where there is no view to receive it.
-     * 
+     *
      * @param event The touch screen event being processed.
-     * 
+     *
      * @return Return true if you have consumed the event, false if you haven't.
      * The default implementation always returns false.
      */
@@ -2496,10 +2499,10 @@
             finish();
             return true;
         }
-        
+
         return false;
     }
-    
+
     /**
      * Called when the trackball was moved and not handled by any of the
      * views inside of the activity.  So, for example, if the trackball moves
@@ -2508,9 +2511,9 @@
      * here happens <em>before</em> trackball movements are converted to
      * DPAD key events, which then get sent back to the view hierarchy, and
      * will be processed at the point for things like focus navigation.
-     * 
+     *
      * @param event The trackball event being processed.
-     * 
+     *
      * @return Return true if you have consumed the event, false if you haven't.
      * The default implementation always returns false.
      */
@@ -2554,21 +2557,21 @@
      * This callback and {@link #onUserLeaveHint} are intended to help
      * activities manage status bar notifications intelligently; specifically,
      * for helping activities determine the proper time to cancel a notfication.
-     * 
+     *
      * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
      * be accompanied by calls to {@link #onUserInteraction}.  This
      * ensures that your activity will be told of relevant user activity such
      * as pulling down the notification pane and touching an item there.
-     * 
+     *
      * <p>Note that this callback will be invoked for the touch down action
      * that begins a touch gesture, but may not be invoked for the touch-moved
      * and touch-up actions that follow.
-     * 
+     *
      * @see #onUserLeaveHint()
      */
     public void onUserInteraction() {
     }
-    
+
     public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
         // Update window manager if: we have a view, that view is
         // attached to its parent (which will be a RootView), and
@@ -2589,14 +2592,14 @@
      * focus.  This is the best indicator of whether this activity is visible
      * to the user.  The default implementation clears the key tracking
      * state, so should always be called.
-     * 
+     *
      * <p>Note that this provides information about global focus state, which
      * is managed independently of activity lifecycles.  As such, while focus
      * changes will generally have some relation to lifecycle changes (an
      * activity that is stopped will not generally get window focus), you
      * should not rely on any particular order between the callbacks here and
      * those in the other lifecycle methods such as {@link #onResume}.
-     * 
+     *
      * <p>As a general rule, however, a resumed activity will have window
      * focus...  unless it has displayed other dialogs or popups that take
      * input focus, in which case the activity itself will not have focus
@@ -2606,14 +2609,14 @@
      * pausing the foreground activity.
      *
      * @param hasFocus Whether the window of this activity has focus.
-     * 
+     *
      * @see #hasWindowFocus()
      * @see #onResume
      * @see View#onWindowFocusChanged(boolean)
      */
     public void onWindowFocusChanged(boolean hasFocus) {
     }
-    
+
     /**
      * Called when the main window associated with the activity has been
      * attached to the window manager.
@@ -2623,7 +2626,7 @@
      */
     public void onAttachedToWindow() {
     }
-    
+
     /**
      * Called when the main window associated with the activity has been
      * detached from the window manager.
@@ -2633,13 +2636,13 @@
      */
     public void onDetachedFromWindow() {
     }
-    
+
     /**
      * Returns true if this activity's <em>main</em> window currently has window focus.
      * Note that this is not the same as the view itself having focus.
-     * 
+     *
      * @return True if this activity's main window currently has window focus.
-     * 
+     *
      * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
      */
     public boolean hasWindowFocus() {
@@ -2661,14 +2664,14 @@
     public void onWindowDismissed() {
         finish();
     }
-    
+
     /**
-     * Called to process key events.  You can override this to intercept all 
-     * key events before they are dispatched to the window.  Be sure to call 
+     * Called to process key events.  You can override this to intercept all
+     * key events before they are dispatched to the window.  Be sure to call
      * this implementation for key events that should be handled normally.
-     * 
+     *
      * @param event The key event.
-     * 
+     *
      * @return boolean Return true if this event was consumed.
      */
     public boolean dispatchKeyEvent(KeyEvent event) {
@@ -2713,9 +2716,9 @@
      * intercept all touch screen events before they are dispatched to the
      * window.  Be sure to call this implementation for touch screen events
      * that should be handled normally.
-     * 
+     *
      * @param ev The touch screen event.
-     * 
+     *
      * @return boolean Return true if this event was consumed.
      */
     public boolean dispatchTouchEvent(MotionEvent ev) {
@@ -2727,15 +2730,15 @@
         }
         return onTouchEvent(ev);
     }
-    
+
     /**
      * Called to process trackball events.  You can override this to
      * intercept all trackball events before they are dispatched to the
      * window.  Be sure to call this implementation for trackball events
      * that should be handled normally.
-     * 
+     *
      * @param ev The trackball event.
-     * 
+     *
      * @return boolean Return true if this event was consumed.
      */
     public boolean dispatchTrackballEvent(MotionEvent ev) {
@@ -2830,7 +2833,7 @@
 
     /**
      * {@inheritDoc}
-     * 
+     *
      * @return The default implementation returns true.
      */
     public boolean onMenuOpened(int featureId, Menu menu) {
@@ -2880,7 +2883,7 @@
                     }
                 }
                 return false;
-                
+
             case Window.FEATURE_CONTEXT_MENU:
                 if(titleCondensed != null) {
                     EventLog.writeEvent(50000, 1, titleCondensed.toString());
@@ -2894,7 +2897,7 @@
                 return false;
         }
     }
-    
+
     /**
      * Default implementation of
      * {@link android.view.Window.Callback#onPanelClosed(int, Menu)} for
@@ -2910,7 +2913,7 @@
                 mFragments.dispatchOptionsMenuClosed(menu);
                 onOptionsMenuClosed(menu);
                 break;
-                
+
             case Window.FEATURE_CONTEXT_MENU:
                 onContextMenuClosed(menu);
                 break;
@@ -2932,32 +2935,32 @@
             mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
         }
     }
-    
+
     /**
      * Initialize the contents of the Activity's standard options menu.  You
      * should place your menu items in to <var>menu</var>.
-     * 
+     *
      * <p>This is only called once, the first time the options menu is
      * displayed.  To update the menu every time it is displayed, see
      * {@link #onPrepareOptionsMenu}.
-     * 
+     *
      * <p>The default implementation populates the menu with standard system
-     * menu items.  These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that 
-     * they will be correctly ordered with application-defined menu items. 
-     * Deriving classes should always call through to the base implementation. 
-     * 
+     * menu items.  These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that
+     * they will be correctly ordered with application-defined menu items.
+     * Deriving classes should always call through to the base implementation.
+     *
      * <p>You can safely hold on to <var>menu</var> (and any items created
      * from it), making modifications to it as desired, until the next
      * time onCreateOptionsMenu() is called.
-     * 
+     *
      * <p>When you add items to the menu, you can implement the Activity's
      * {@link #onOptionsItemSelected} method to handle them there.
-     * 
+     *
      * @param menu The options menu in which you place your items.
-     * 
+     *
      * @return You must return true for the menu to be displayed;
      *         if you return false it will not be shown.
-     * 
+     *
      * @see #onPrepareOptionsMenu
      * @see #onOptionsItemSelected
      */
@@ -2973,17 +2976,17 @@
      * called right before the menu is shown, every time it is shown.  You can
      * use this method to efficiently enable/disable items or otherwise
      * dynamically modify the contents.
-     * 
+     *
      * <p>The default implementation updates the system menu items based on the
      * activity's state.  Deriving classes should always call through to the
      * base class implementation.
-     * 
+     *
      * @param menu The options menu as last shown or first initialized by
      *             onCreateOptionsMenu().
-     * 
+     *
      * @return You must return true for the menu to be displayed;
      *         if you return false it will not be shown.
-     * 
+     *
      * @see #onCreateOptionsMenu
      */
     public boolean onPrepareOptionsMenu(Menu menu) {
@@ -3000,15 +3003,15 @@
      * its Handler as appropriate).  You can use this method for any items
      * for which you would like to do processing without those other
      * facilities.
-     * 
+     *
      * <p>Derived classes should call through to the base class for it to
      * perform the default menu handling.</p>
-     * 
+     *
      * @param item The menu item that was selected.
-     * 
+     *
      * @return boolean Return false to allow normal menu processing to
      *         proceed, true to consume it here.
-     * 
+     *
      * @see #onCreateOptionsMenu
      */
     public boolean onOptionsItemSelected(MenuItem item) {
@@ -3125,7 +3128,7 @@
     /**
      * This hook is called whenever the options menu is being closed (either by the user canceling
      * the menu with the back/menu button, or when an item is selected).
-     *  
+     *
      * @param menu The options menu as last shown or first initialized by
      *             onCreateOptionsMenu().
      */
@@ -3134,7 +3137,7 @@
             mParent.onOptionsMenuClosed(menu);
         }
     }
-    
+
     /**
      * Programmatically opens the options menu. If the options menu is already
      * open, this method does nothing.
@@ -3144,7 +3147,7 @@
             mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
         }
     }
-    
+
     /**
      * Progammatically closes the options menu. If the options menu is already
      * closed, this method does nothing.
@@ -3175,43 +3178,43 @@
      * {@link OnCreateContextMenuListener} on the view to this activity, so
      * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
      * called when it is time to show the context menu.
-     * 
+     *
      * @see #unregisterForContextMenu(View)
      * @param view The view that should show a context menu.
      */
     public void registerForContextMenu(View view) {
         view.setOnCreateContextMenuListener(this);
     }
-    
+
     /**
      * Prevents a context menu to be shown for the given view. This method will remove the
      * {@link OnCreateContextMenuListener} on the view.
-     * 
+     *
      * @see #registerForContextMenu(View)
      * @param view The view that should stop showing a context menu.
      */
     public void unregisterForContextMenu(View view) {
         view.setOnCreateContextMenuListener(null);
     }
-    
+
     /**
      * Programmatically opens the context menu for a particular {@code view}.
      * The {@code view} should have been added via
      * {@link #registerForContextMenu(View)}.
-     * 
+     *
      * @param view The view to show the context menu for.
      */
     public void openContextMenu(View view) {
         view.showContextMenu();
     }
-    
+
     /**
      * Programmatically closes the most recently opened context menu, if showing.
      */
     public void closeContextMenu() {
         mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
     }
-    
+
     /**
      * This hook is called whenever an item in a context menu is selected. The
      * default implementation simply returns false to have the normal processing
@@ -3224,7 +3227,7 @@
      * <p>
      * Derived classes should call through to the base class for it to perform
      * the default menu handling.
-     * 
+     *
      * @param item The context menu item that was selected.
      * @return boolean Return false to allow normal context menu processing to
      *         proceed, true to consume it here.
@@ -3240,7 +3243,7 @@
      * This hook is called whenever the context menu is being closed (either by
      * the user canceling the menu with the back/menu button, or when an item is
      * selected).
-     * 
+     *
      * @param menu The context menu that is being closed.
      */
     public void onContextMenuClosed(Menu menu) {
@@ -3309,14 +3312,14 @@
      * Provides an opportunity to prepare a managed dialog before it is being
      * shown.  The default implementation calls through to
      * {@link #onPrepareDialog(int, Dialog)} for compatibility.
-     * 
+     *
      * <p>
      * Override this if you need to update a managed dialog based on the state
      * of the application each time it is shown. For example, a time picker
      * dialog might want to be updated with the current time. You should call
      * through to the superclass's implementation. The default implementation
      * will set this Activity as the owner activity on the Dialog.
-     * 
+     *
      * @param id The id of the managed dialog.
      * @param dialog The dialog.
      * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
@@ -3367,7 +3370,7 @@
      * If you need to rebuild the dialog, call {@link #removeDialog(int)} first.
      * @return Returns true if the Dialog was created; false is returned if
      * it is not created because {@link #onCreateDialog(int, Bundle)} returns false.
-     * 
+     *
      * @see Dialog
      * @see #onCreateDialog(int, Bundle)
      * @see #onPrepareDialog(int, Dialog, Bundle)
@@ -3393,7 +3396,7 @@
             }
             mManagedDialogs.put(id, md);
         }
-        
+
         md.mArgs = args;
         onPrepareDialog(id, md.mDialog, args);
         md.mDialog.show();
@@ -3422,7 +3425,7 @@
         if (mManagedDialogs == null) {
             throw missingDialog(id);
         }
-        
+
         final ManagedDialog md = mManagedDialogs.get(id);
         if (md == null) {
             throw missingDialog(id);
@@ -3449,7 +3452,7 @@
      * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, this function
      * will not throw an exception if you try to remove an ID that does not
      * currently have an associated dialog.</p>
-     * 
+     *
      * @param id The id of the managed dialog.
      *
      * @see #onCreateDialog(int, Bundle)
@@ -3474,37 +3477,37 @@
 
     /**
      * This hook is called when the user signals the desire to start a search.
-     * 
+     *
      * <p>You can use this function as a simple way to launch the search UI, in response to a
-     * menu item, search button, or other widgets within your activity. Unless overidden, 
+     * menu item, search button, or other widgets within your activity. Unless overidden,
      * calling this function is the same as calling
      * {@link #startSearch startSearch(null, false, null, false)}, which launches
      * search for the current activity as specified in its manifest, see {@link SearchManager}.
-     * 
+     *
      * <p>You can override this function to force global search, e.g. in response to a dedicated
      * search key, or to block search entirely (by simply returning false).
-     * 
+     *
      * @return Returns {@code true} if search launched, and {@code false} if activity blocks it.
      *         The default implementation always returns {@code true}.
-     * 
+     *
      * @see android.app.SearchManager
      */
     public boolean onSearchRequested() {
-        startSearch(null, false, null, false); 
+        startSearch(null, false, null, false);
         return true;
     }
-    
+
     /**
      * This hook is called to launch the search UI.
-     * 
-     * <p>It is typically called from onSearchRequested(), either directly from 
-     * Activity.onSearchRequested() or from an overridden version in any given 
+     *
+     * <p>It is typically called from onSearchRequested(), either directly from
+     * Activity.onSearchRequested() or from an overridden version in any given
      * Activity.  If your goal is simply to activate search, it is preferred to call
      * onSearchRequested(), which may have been overridden elsewhere in your Activity.  If your goal
      * is to inject specific data such as context data, it is preferred to <i>override</i>
      * onSearchRequested(), so that any callers to it will benefit from the override.
-     * 
-     * @param initialQuery Any non-null non-empty string will be inserted as 
+     *
+     * @param initialQuery Any non-null non-empty string will be inserted as
      * pre-entered text in the search query box.
      * @param selectInitialQuery If true, the initial query will be preselected, which means that
      * any further typing will replace it.  This is useful for cases where an entire pre-formed
@@ -3512,15 +3515,15 @@
      * inserted query.  This is useful when the inserted query is text that the user entered,
      * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
      * if initialQuery is a non-empty string.</i>
-     * @param appSearchData An application can insert application-specific 
-     * context here, in order to improve quality or specificity of its own 
+     * @param appSearchData An application can insert application-specific
+     * context here, in order to improve quality or specificity of its own
      * searches.  This data will be returned with SEARCH intent(s).  Null if
      * no extra data is required.
      * @param globalSearch If false, this will only launch the search that has been specifically
-     * defined by the application (which is usually defined as a local search).  If no default 
+     * defined by the application (which is usually defined as a local search).  If no default
      * search is defined in the current application or activity, global search will be launched.
      * If true, this will always launch a platform-global (e.g. web-based) search instead.
-     * 
+     *
      * @see android.app.SearchManager
      * @see #onSearchRequested
      */
@@ -3528,7 +3531,7 @@
             @Nullable Bundle appSearchData, boolean globalSearch) {
         ensureSearchManager();
         mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
-                appSearchData, globalSearch); 
+                appSearchData, globalSearch);
     }
 
     /**
@@ -3550,7 +3553,7 @@
      * Request that key events come to this activity. Use this if your
      * activity has no views with focus, but the activity still wants
      * a chance to process key events.
-     * 
+     *
      * @see android.view.Window#takeKeyEvents
      */
     public void takeKeyEvents(boolean get) {
@@ -3560,12 +3563,12 @@
     /**
      * Enable extended window features.  This is a convenience for calling
      * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
-     * 
+     *
      * @param featureId The desired feature as defined in
      *                  {@link android.view.Window}.
      * @return Returns true if the requested feature is supported and now
      *         enabled.
-     * 
+     *
      * @see android.view.Window#requestFeature
      */
     public final boolean requestWindowFeature(int featureId) {
@@ -3677,7 +3680,7 @@
      * Launch an activity for which you would like a result when it finished.
      * When this activity exits, your
      * onActivityResult() method will be called with the given requestCode.
-     * Using a negative requestCode is the same as calling 
+     * Using a negative requestCode is the same as calling
      * {@link #startActivity} (the activity is not launched as a sub-activity).
      *
      * <p>Note that this method should only be used with Intent protocols
@@ -3687,7 +3690,7 @@
      * are launching uses the singleTask launch mode, it will not run in your
      * task and thus you will immediately receive a cancel result.
      *
-     * <p>As a special case, if you call startActivityForResult() with a requestCode 
+     * <p>As a special case, if you call startActivityForResult() with a requestCode
      * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
      * activity, then your window will not be displayed until a result is
      * returned back from the started activity.  This is to avoid visible
@@ -3751,6 +3754,48 @@
     /**
      * @hide Implement to provide correct calling token.
      */
+    public void startActivityForResultAsUser(Intent intent, int requestCode, UserHandle user) {
+        startActivityForResultAsUser(intent, requestCode, null, user);
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
+    public void startActivityForResultAsUser(Intent intent, int requestCode,
+            @Nullable Bundle options, UserHandle user) {
+        if (options != null) {
+            mActivityTransitionState.startExitOutTransition(this, options);
+        }
+        if (mParent != null) {
+            throw new RuntimeException("Can't be called from a child");
+        }
+        Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
+                this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode,
+                options, user);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
+        }
+        if (requestCode >= 0) {
+            // If this start is requesting a result, we can avoid making
+            // the activity visible until the result is received.  Setting
+            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+            // activity hidden during this time, to avoid flickering.
+            // This can only be done when a result is requested because
+            // that guarantees we will get information back when the
+            // activity is finished, no matter what happens to it.
+            mStartedActivity = true;
+        }
+
+        final View decor = mWindow != null ? mWindow.peekDecorView() : null;
+        if (decor != null) {
+            decor.cancelPendingInputEvents();
+        }
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
     public void startActivityAsUser(Intent intent, UserHandle user) {
         startActivityAsUser(intent, null, user);
     }
@@ -3760,7 +3805,7 @@
      */
     public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
         if (mParent != null) {
-            throw new RuntimeException("Called be called from a child");
+            throw new RuntimeException("Can't be called from a child");
         }
         Instrumentation.ActivityResult ar =
                 mInstrumentation.execStartActivity(
@@ -3803,7 +3848,7 @@
      * here; otherwise, its associated action will be executed (such as
      * sending a broadcast) as if you had called
      * {@link IntentSender#sendIntent IntentSender.sendIntent} on it.
-     * 
+     *
      * @param intent The IntentSender to launch.
      * @param requestCode If >= 0, this code will be returned in
      *                    onActivityResult() when the activity exits.
@@ -3894,19 +3939,19 @@
      * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
      * required; if not specified, the new activity will be added to the
      * task of the caller.
-     * 
+     *
      * <p>This method throws {@link android.content.ActivityNotFoundException}
      * if there was no Activity found to run the given Intent.
-     * 
-     * @param intent The intent to start. 
+     *
+     * @param intent The intent to start.
      * @param options Additional options for how the Activity should be started.
      * See {@link android.content.Context#startActivity(Intent, Bundle)
      * Context.startActivity(Intent, Bundle)} for more details.
-     * 
+     *
      * @throws android.content.ActivityNotFoundException
      *
      * @see {@link #startActivity(Intent)}
-     * @see #startActivityForResult 
+     * @see #startActivityForResult
      */
     @Override
     public void startActivity(Intent intent, @Nullable Bundle options) {
@@ -3966,7 +4011,7 @@
     /**
      * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
      * with no options.
-     * 
+     *
      * @param intent The IntentSender to launch.
      * @param fillInIntent If non-null, this will be provided as the
      * intent parameter to {@link IntentSender#sendIntent}.
@@ -4039,19 +4084,19 @@
     /**
      * A special variation to launch an activity only if a new activity
      * instance is needed to handle the given Intent.  In other words, this is
-     * just like {@link #startActivityForResult(Intent, int)} except: if you are 
+     * just like {@link #startActivityForResult(Intent, int)} except: if you are
      * using the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag, or
-     * singleTask or singleTop 
+     * singleTask or singleTop
      * {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode},
-     * and the activity 
-     * that handles <var>intent</var> is the same as your currently running 
-     * activity, then a new instance is not needed.  In this case, instead of 
-     * the normal behavior of calling {@link #onNewIntent} this function will 
-     * return and you can handle the Intent yourself. 
-     * 
+     * and the activity
+     * that handles <var>intent</var> is the same as your currently running
+     * activity, then a new instance is not needed.  In this case, instead of
+     * the normal behavior of calling {@link #onNewIntent} this function will
+     * return and you can handle the Intent yourself.
+     *
      * <p>This function can only be called from a top-level activity; if it is
      * called from a child activity, a runtime exception will be thrown.
-     * 
+     *
      * @param intent The intent to start.
      * @param requestCode If >= 0, this code will be returned in
      *         onActivityResult() when the activity exits, as described in
@@ -4059,10 +4104,10 @@
      * @param options Additional options for how the Activity should be started.
      * See {@link android.content.Context#startActivity(Intent, Bundle)
      * Context.startActivity(Intent, Bundle)} for more details.
-     * 
+     *
      * @return If a new activity was launched then true is returned; otherwise
      *         false is returned and you must handle the Intent yourself.
-     *  
+     *
      * @see #startActivity
      * @see #startActivityForResult
      */
@@ -4125,7 +4170,7 @@
      * other activity components.  You can use this to hand the Intent off
      * to the next Activity that can handle it.  You typically call this in
      * {@link #onCreate} with the Intent returned by {@link #getIntent}.
-     * 
+     *
      * @param intent The intent to dispatch to the next activity.  For
      * correct behavior, this must be the same as the Intent that started
      * your own activity; the only changes you can make are to the extras
@@ -4133,7 +4178,7 @@
      * @param options Additional options for how the Activity should be started.
      * See {@link android.content.Context#startActivity(Intent, Bundle)
      * Context.startActivity(Intent, Bundle)} for more details.
-     * 
+     *
      * @return Returns a boolean indicating whether there was another Activity
      * to start: true if there was a next activity to start, false if there
      * wasn't.  In general, if true is returned you will then want to call
@@ -4175,23 +4220,23 @@
     }
 
     /**
-     * This is called when a child activity of this one calls its 
+     * This is called when a child activity of this one calls its
      * {@link #startActivity} or {@link #startActivityForResult} method.
-     * 
+     *
      * <p>This method throws {@link android.content.ActivityNotFoundException}
      * if there was no Activity found to run the given Intent.
-     * 
+     *
      * @param child The activity making the call.
      * @param intent The intent to start.
      * @param requestCode Reply request code.  < 0 if reply is not requested.
      * @param options Additional options for how the Activity should be started.
      * See {@link android.content.Context#startActivity(Intent, Bundle)
      * Context.startActivity(Intent, Bundle)} for more details.
-     * 
+     *
      * @throws android.content.ActivityNotFoundException
-     * 
-     * @see #startActivity 
-     * @see #startActivityForResult 
+     *
+     * @see #startActivity
+     * @see #startActivityForResult
      */
     public void startActivityFromChild(@NonNull Activity child, Intent intent,
             int requestCode, @Nullable Bundle options) {
@@ -4225,24 +4270,24 @@
     }
 
     /**
-     * This is called when a Fragment in this activity calls its 
+     * This is called when a Fragment in this activity calls its
      * {@link Fragment#startActivity} or {@link Fragment#startActivityForResult}
      * method.
-     * 
+     *
      * <p>This method throws {@link android.content.ActivityNotFoundException}
      * if there was no Activity found to run the given Intent.
-     * 
+     *
      * @param fragment The fragment making the call.
      * @param intent The intent to start.
-     * @param requestCode Reply request code.  < 0 if reply is not requested. 
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
      * @param options Additional options for how the Activity should be started.
      * See {@link android.content.Context#startActivity(Intent, Bundle)
      * Context.startActivity(Intent, Bundle)} for more details.
-     * 
+     *
      * @throws android.content.ActivityNotFoundException
-     * 
-     * @see Fragment#startActivity 
-     * @see Fragment#startActivityForResult 
+     *
+     * @see Fragment#startActivity
+     * @see Fragment#startActivityForResult
      */
     public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
             int requestCode, @Nullable Bundle options) {
@@ -4310,14 +4355,14 @@
         } catch (RemoteException e) {
         }
     }
-    
+
     /**
      * Call this to set the result that your activity will return to its
      * caller.
-     * 
+     *
      * @param resultCode The result code to propagate back to the originating
      *                   activity, often RESULT_CANCELED or RESULT_OK
-     * 
+     *
      * @see #RESULT_CANCELED
      * @see #RESULT_OK
      * @see #RESULT_FIRST_USER
@@ -4346,7 +4391,7 @@
      * @param resultCode The result code to propagate back to the originating
      *                   activity, often RESULT_CANCELED or RESULT_OK
      * @param data The data to propagate back to the originating activity.
-     * 
+     *
      * @see #RESULT_CANCELED
      * @see #RESULT_OK
      * @see #RESULT_FIRST_USER
@@ -4364,10 +4409,10 @@
      * the data in {@link #setResult setResult()} will be sent to.  You can
      * use this information to validate that the recipient is allowed to
      * receive the data.
-     * 
+     *
      * <p class="note">Note: if the calling activity is not expecting a result (that is it
-     * did not use the {@link #startActivityForResult} 
-     * form that includes a request code), then the calling package will be 
+     * did not use the {@link #startActivityForResult}
+     * form that includes a request code), then the calling package will be
      * null.</p>
      *
      * <p class="note">Note: prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
@@ -4375,7 +4420,7 @@
      * package was no longer running, it would return null instead of the proper package
      * name.  You can use {@link #getCallingActivity()} and retrieve the package name
      * from that instead.</p>
-     * 
+     *
      * @return The package of the activity that will receive your
      *         reply, or null if none.
      */
@@ -4393,12 +4438,12 @@
      * who the data in {@link #setResult setResult()} will be sent to.  You
      * can use this information to validate that the recipient is allowed to
      * receive the data.
-     * 
+     *
      * <p class="note">Note: if the calling activity is not expecting a result (that is it
-     * did not use the {@link #startActivityForResult} 
-     * form that includes a request code), then the calling package will be 
-     * null. 
-     * 
+     * did not use the {@link #startActivityForResult}
+     * form that includes a request code), then the calling package will be
+     * null.
+     *
      * @return The ComponentName of the activity that will receive your
      *         reply, or null if none.
      */
@@ -4417,7 +4462,7 @@
      * UI itself, but can't just finish prior to onResume() because it needs
      * to wait for a service binding or such.  Setting this to false allows
      * you to prevent your UI from being shown during that time.
-     * 
+     *
      * <p>The default value for this is taken from the
      * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme.
      */
@@ -4430,7 +4475,7 @@
             }
         }
     }
-    
+
     void makeVisible() {
         if (!mWindowAdded) {
             ViewManager wm = getWindowManager();
@@ -4439,16 +4484,16 @@
         }
         mDecor.setVisibility(View.VISIBLE);
     }
-    
+
     /**
      * Check to see whether this activity is in the process of finishing,
      * either because you called {@link #finish} on it or someone else
      * has requested that it finished.  This is often used in
      * {@link #onPause} to determine whether the activity is simply pausing or
      * completely finishing.
-     * 
+     *
      * @return If the activity is finishing, returns true; else returns false.
-     * 
+     *
      * @see #finish
      */
     public boolean isFinishing() {
@@ -4468,7 +4513,7 @@
      * recreated with a new configuration. This is often used in
      * {@link #onStop} to determine whether the state needs to be cleaned up or will be passed
      * on to the next instance of the activity via {@link #onRetainNonConfigurationInstance()}.
-     * 
+     *
      * @return If the activity is being torn down in order to be recreated with a new configuration,
      * returns true; else returns false.
      */
@@ -4561,12 +4606,12 @@
     }
 
     /**
-     * This is called when a child activity of this one calls its 
+     * This is called when a child activity of this one calls its
      * {@link #finish} method.  The default implementation simply calls
      * finish() on this activity (the parent), finishing the entire group.
-     * 
+     *
      * @param child The activity making the call.
-     * 
+     *
      * @see #finish
      */
     public void finishFromChild(Activity child) {
@@ -4589,7 +4634,7 @@
     /**
      * Force finish another activity that you had previously started with
      * {@link #startActivityForResult}.
-     * 
+     *
      * @param requestCode The request code of the activity that you had
      *                    given to startActivityForResult().  If there are multiple
      *                    activities started with this request code, they
@@ -4611,7 +4656,7 @@
     /**
      * This is called when a child activity of this one calls its
      * finishActivity().
-     * 
+     *
      * @param child The activity making the call.
      * @param requestCode Request code that had been used to start the
      *                    activity.
@@ -4639,10 +4684,10 @@
      * data from it.  The <var>resultCode</var> will be
      * {@link #RESULT_CANCELED} if the activity explicitly returned that,
      * didn't return any result, or crashed during its operation.
-     * 
+     *
      * <p>You will receive this call immediately before onResume() when your
      * activity is re-starting.
-     * 
+     *
      * @param requestCode The integer request code originally supplied to
      *                    startActivityForResult(), allowing you to identify who this
      *                    result came from.
@@ -4650,7 +4695,7 @@
      *                   through its setResult().
      * @param data An Intent, which can return result data to the caller
      *               (various data can be attached to Intent "extras").
-     * 
+     *
      * @see #startActivityForResult
      * @see #createPendingResult
      * @see #setResult(int)
@@ -4680,12 +4725,12 @@
     }
 
     /**
-     * Create a new PendingIntent object which you can hand to others 
-     * for them to use to send result data back to your 
-     * {@link #onActivityResult} callback.  The created object will be either 
-     * one-shot (becoming invalid after a result is sent back) or multiple 
-     * (allowing any number of results to be sent through it). 
-     *  
+     * Create a new PendingIntent object which you can hand to others
+     * for them to use to send result data back to your
+     * {@link #onActivityResult} callback.  The created object will be either
+     * one-shot (becoming invalid after a result is sent back) or multiple
+     * (allowing any number of results to be sent through it).
+     *
      * @param requestCode Private request code for the sender that will be
      * associated with the result data when it is returned.  The sender can not
      * modify this value, allowing you to identify incoming results.
@@ -4698,12 +4743,12 @@
      * or any of the flags as supported by
      * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
      * of the intent that can be supplied when the actual send happens.
-     * 
+     *
      * @return Returns an existing or new PendingIntent matching the given
      * parameters.  May return null only if
      * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE} has been
      * supplied.
-     * 
+     *
      * @see PendingIntent
      */
     public PendingIntent createPendingResult(int requestCode, @NonNull Intent data,
@@ -4730,7 +4775,7 @@
      * orientation, the screen will immediately be changed (possibly causing
      * the activity to be restarted). Otherwise, this will be used the next
      * time the activity is visible.
-     * 
+     *
      * @param requestedOrientation An orientation constant as used in
      * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
      */
@@ -4746,13 +4791,13 @@
             mParent.setRequestedOrientation(requestedOrientation);
         }
     }
-    
+
     /**
      * Return the current requested orientation of the activity.  This will
      * either be the orientation requested in its component's manifest, or
      * the last requested orientation given to
      * {@link #setRequestedOrientation(int)}.
-     * 
+     *
      * @return Returns an orientation constant as used in
      * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
      */
@@ -4770,11 +4815,11 @@
         }
         return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
     }
-    
+
     /**
      * Return the identifier of the task this activity is in.  This identifier
      * will remain the same for the lifetime of the activity.
-     * 
+     *
      * @return Task identifier, an opaque integer.
      */
     public int getTaskId() {
@@ -4789,7 +4834,7 @@
     /**
      * Return whether this activity is the root of a task.  The root is the
      * first activity in a task.
-     * 
+     *
      * @return True if this is the root activity, else false.
      */
     public boolean isTaskRoot() {
@@ -4804,11 +4849,11 @@
     /**
      * Move the task containing this activity to the back of the activity
      * stack.  The activity's order within the task is unchanged.
-     * 
+     *
      * @param nonRoot If false then this only works if the activity is the root
      *                of a task; if true it will work for any activity in
      *                a task.
-     * 
+     *
      * @return If the task was moved (or it was already at the
      *         back) true is returned, else false.
      */
@@ -4825,7 +4870,7 @@
     /**
      * Returns class name for this activity with the package prefix removed.
      * This is the default name used to read and write settings.
-     * 
+     *
      * @return The local class name.
      */
     @NonNull
@@ -4839,10 +4884,10 @@
         }
         return cls.substring(packageLen+1);
     }
-    
+
     /**
      * Returns complete component name of this activity.
-     * 
+     *
      * @return Returns the complete component name for this activity
      */
     public ComponentName getComponentName()
@@ -4855,9 +4900,9 @@
      * that are private to this activity.  This simply calls the underlying
      * {@link #getSharedPreferences(String, int)} method by passing in this activity's
      * class name as the preferences name.
-     * 
-     * @param mode Operating mode.  Use {@link #MODE_PRIVATE} for the default 
-     *             operation, {@link #MODE_WORLD_READABLE} and 
+     *
+     * @param mode Operating mode.  Use {@link #MODE_PRIVATE} for the default
+     *             operation, {@link #MODE_WORLD_READABLE} and
      *             {@link #MODE_WORLD_WRITEABLE} to control permissions.
      *
      * @return Returns the single SharedPreferences instance that can be used
@@ -4866,12 +4911,12 @@
     public SharedPreferences getPreferences(int mode) {
         return getSharedPreferences(getLocalClassName(), mode);
     }
-    
+
     private void ensureSearchManager() {
         if (mSearchManager != null) {
             return;
         }
-        
+
         mSearchManager = new SearchManager(this, null);
     }
 
@@ -4989,7 +5034,7 @@
      * <p>
      * In order for the progress bar to be shown, the feature must be requested
      * via {@link #requestWindowFeature(int)}.
-     * 
+     *
      * @param visible Whether to show the progress bars in the title.
      */
     public final void setProgressBarVisibility(boolean visible) {
@@ -5009,14 +5054,14 @@
         getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
                 visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF);
     }
-    
+
     /**
      * Sets whether the horizontal progress bar in the title should be indeterminate (the circular
      * is always indeterminate).
      * <p>
      * In order for the progress bar to be shown, the feature must be requested
      * via {@link #requestWindowFeature(int)}.
-     * 
+     *
      * @param indeterminate Whether the horizontal progress bar should be indeterminate.
      */
     public final void setProgressBarIndeterminate(boolean indeterminate) {
@@ -5024,13 +5069,13 @@
                 indeterminate ? Window.PROGRESS_INDETERMINATE_ON
                         : Window.PROGRESS_INDETERMINATE_OFF);
     }
-    
+
     /**
      * Sets the progress for the progress bars in the title.
      * <p>
      * In order for the progress bar to be shown, the feature must be requested
      * via {@link #requestWindowFeature(int)}.
-     * 
+     *
      * @param progress The progress for the progress bar. Valid ranges are from
      *            0 to 10000 (both inclusive). If 10000 is given, the progress
      *            bar will be completely filled and will fade out.
@@ -5038,7 +5083,7 @@
     public final void setProgress(int progress) {
         getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START);
     }
-    
+
     /**
      * Sets the secondary progress for the progress bar in the title. This
      * progress is drawn between the primary progress (set via
@@ -5048,7 +5093,7 @@
      * <p>
      * In order for the progress bar to be shown, the feature must be requested
      * via {@link #requestWindowFeature(int)}.
-     * 
+     *
      * @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from
      *            0 to 10000 (both inclusive).
      */
@@ -5062,16 +5107,16 @@
      * volume controls.
      * <p>
      * The suggested audio stream will be tied to the window of this Activity.
-     * If the Activity is switched, the stream set here is no longer the
-     * suggested stream. The client does not need to save and restore the old
-     * suggested stream value in onPause and onResume.
-     * 
+     * Volume requests which are received while the Activity is in the
+     * foreground will affect this stream.
+     * <p>
+     * It is not guaranteed that the hardware volume controls will always change
+     * this stream's volume (for example, if a call is in progress, its stream's
+     * volume may be changed instead). To reset back to the default, use
+     * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
+     *
      * @param streamType The type of the audio stream whose volume should be
-     *        changed by the hardware volume controls. It is not guaranteed that
-     *        the hardware volume controls will always change this stream's
-     *        volume (for example, if a call is in progress, its stream's volume
-     *        may be changed instead). To reset back to the default, use
-     *        {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
+     *            changed by the hardware volume controls.
      */
     public final void setVolumeControlStream(int streamType) {
         getWindow().setVolumeControlStream(streamType);
@@ -5080,7 +5125,7 @@
     /**
      * Gets the suggested audio stream whose volume should be changed by the
      * hardware volume controls.
-     * 
+     *
      * @return The suggested audio stream type whose volume should be changed by
      *         the hardware volume controls.
      * @see #setVolumeControlStream(int)
@@ -5088,7 +5133,40 @@
     public final int getVolumeControlStream() {
         return getWindow().getVolumeControlStream();
     }
-    
+
+    /**
+     * Sets a {@link MediaController} to send media keys and volume changes to.
+     * <p>
+     * The controller will be tied to the window of this Activity. Media key and
+     * volume events which are received while the Activity is in the foreground
+     * will be forwarded to the controller and used to invoke transport controls
+     * or adjust the volume. This may be used instead of or in addition to
+     * {@link #setVolumeControlStream} to affect a specific session instead of a
+     * specific stream.
+     * <p>
+     * It is not guaranteed that the hardware volume controls will always change
+     * this session's volume (for example, if a call is in progress, its
+     * stream's volume may be changed instead). To reset back to the default use
+     * null as the controller.
+     *
+     * @param controller The controller for the session which should receive
+     *            media keys and volume changes.
+     */
+    public final void setMediaController(MediaController controller) {
+        getWindow().setMediaController(controller);
+    }
+
+    /**
+     * Gets the controller which should be receiving media key and volume events
+     * while this activity is in the foreground.
+     *
+     * @return The controller which should receive events.
+     * @see #setMediaController(android.media.session.MediaController)
+     */
+    public final MediaController getMediaController() {
+        return getWindow().getMediaController();
+    }
+
     /**
      * Runs the specified action on the UI thread. If the current thread is the UI
      * thread, then the action is executed immediately. If the current thread is
@@ -5134,7 +5212,7 @@
         if (!"fragment".equals(name)) {
             return onCreateView(name, context, attrs);
         }
-        
+
         return mFragments.onCreateView(parent, name, context, attrs);
     }
 
@@ -5695,7 +5773,7 @@
     }
 
     // ------------------ Internal API ------------------
-    
+
     final void setParent(Activity parent) {
         mParent = parent;
     }
@@ -5709,7 +5787,7 @@
         attachBaseContext(context);
 
         mFragments.attachActivity(this, mContainer, null);
-        
+
         mWindow = PolicyManager.makeNewWindow(this);
         mWindow.setCallback(this);
         mWindow.setOnWindowDismissedCallback(this);
@@ -5804,7 +5882,7 @@
         }
         mActivityTransitionState.enterReady(this);
     }
-    
+
     final void performRestart() {
         mFragments.noteStateNotSaved();
 
@@ -5843,14 +5921,14 @@
             performStart();
         }
     }
-    
+
     final void performResume() {
         performRestart();
-        
+
         mFragments.execPendingActions();
-        
+
         mLastNonConfigurationInstances = null;
-        
+
         mCalled = false;
         // mResumed is set by the instrumentation
         mInstrumentation.callActivityOnResume(this);
@@ -5862,10 +5940,10 @@
 
         // Now really resume, and install the current status bar and menu.
         mCalled = false;
-        
+
         mFragments.dispatchResume();
         mFragments.execPendingActions();
-        
+
         onPostResume();
         if (!mCalled) {
             throw new SuperNotCalledException(
@@ -5888,12 +5966,12 @@
         }
         mResumed = false;
     }
-    
+
     final void performUserLeaving() {
         onUserInteraction();
         onUserLeaveHint();
     }
-    
+
     final void performStop() {
         mDoReportFullyDrawn = false;
         if (mLoadersStarted) {
@@ -5906,7 +5984,7 @@
                 }
             }
         }
-        
+
         if (!mStopped) {
             if (mWindow != null) {
                 mWindow.closeAllPanels();
@@ -5915,9 +5993,9 @@
             if (mToken != null && mParent == null) {
                 WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
             }
-            
+
             mFragments.dispatchStop();
-            
+
             mCalled = false;
             mInstrumentation.callActivityOnStop(this);
             if (!mCalled) {
@@ -5925,7 +6003,7 @@
                     "Activity " + mComponent.toShortString() +
                     " did not call through to super.onStop()");
             }
-    
+
             synchronized (mManagedCursors) {
                 final int N = mManagedCursors.size();
                 for (int i=0; i<N; i++) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e1d0b86..c8cab6f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2456,7 +2456,10 @@
     }
 
     /**
-     * @hide
+     * Return whether currently in lock task mode.  When in this mode
+     * no new tasks can be created or switched to.
+     *
+     * @see Activity#startLockTask()
      */
     public boolean isInLockTaskMode() {
         try {
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/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 15be9b1..e074219 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -403,20 +403,18 @@
         view.layout(left, top, right, bottom);
     }
 
-    protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
+    protected ArrayList<SharedElementOriginalState> setSharedElementState(
             Bundle sharedElementState, final ArrayList<View> snapshots) {
-        ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
-                new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
+        ArrayList<SharedElementOriginalState> originalImageState =
+                new ArrayList<SharedElementOriginalState>();
         if (sharedElementState != null) {
             int[] tempLoc = new int[2];
             for (int i = 0; i < mSharedElementNames.size(); i++) {
                 View sharedElement = mSharedElements.get(i);
                 String name = mSharedElementNames.get(i);
-                Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
+                SharedElementOriginalState originalState = getOldSharedElementState(sharedElement,
                         name, sharedElementState);
-                if (originalState != null) {
-                    originalImageState.put((ImageView) sharedElement, originalState);
-                }
+                originalImageState.add(originalState);
                 View parent = (View) sharedElement.getParent();
                 parent.getLocationOnScreen(tempLoc);
                 setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
@@ -438,29 +436,34 @@
         return originalImageState;
     }
 
-    private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
+    private static SharedElementOriginalState getOldSharedElementState(View view, String name,
             Bundle transitionArgs) {
+
+        SharedElementOriginalState state = new SharedElementOriginalState();
+        state.mLeft = view.getLeft();
+        state.mTop = view.getTop();
+        state.mRight = view.getRight();
+        state.mBottom = view.getBottom();
+        state.mMeasuredWidth = view.getMeasuredWidth();
+        state.mMeasuredHeight = view.getMeasuredHeight();
         if (!(view instanceof ImageView)) {
-            return null;
+            return state;
         }
         Bundle bundle = transitionArgs.getBundle(name);
         if (bundle == null) {
-            return null;
+            return state;
         }
         int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
         if (scaleTypeInt < 0) {
-            return null;
+            return state;
         }
 
         ImageView imageView = (ImageView) view;
-        ImageView.ScaleType originalScaleType = imageView.getScaleType();
-
-        Matrix originalMatrix = null;
-        if (originalScaleType == ImageView.ScaleType.MATRIX) {
-            originalMatrix = new Matrix(imageView.getImageMatrix());
+        state.mScaleType = imageView.getScaleType();
+        if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+            state.mMatrix = new Matrix(imageView.getImageMatrix());
         }
-
-        return Pair.create(originalScaleType, originalMatrix);
+        return state;
     }
 
     protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
@@ -489,13 +492,26 @@
         return snapshots;
     }
 
-    protected static void setOriginalImageViewState(
-            ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
+    protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
+            ArrayList<SharedElementOriginalState> originalState) {
         for (int i = 0; i < originalState.size(); i++) {
-            ImageView imageView = originalState.keyAt(i);
-            Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
-            imageView.setScaleType(state.first);
-            imageView.setImageMatrix(state.second);
+            View view = sharedElements.get(i);
+            SharedElementOriginalState state = originalState.get(i);
+            if (view instanceof ImageView && state.mScaleType != null) {
+                ImageView imageView = (ImageView) view;
+                imageView.setScaleType(state.mScaleType);
+                if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+                  imageView.setImageMatrix(state.mMatrix);
+                }
+            }
+            // origignal widthspec might be AT_MOST,  but it should work for most
+            // cases.
+            int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
+                    View.MeasureSpec.EXACTLY);
+            int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
+                    View.MeasureSpec.EXACTLY);
+            view.measure(widthSpec, heightSpec);
+            view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
         }
     }
 
@@ -622,4 +638,15 @@
         }
     }
 
+    static class SharedElementOriginalState {
+        int mLeft;
+        int mTop;
+        int mRight;
+        int mBottom;
+        int mMeasuredWidth;
+        int mMeasuredHeight;
+        ImageView.ScaleType mScaleType;
+        Matrix mMatrix;
+    }
+
 }
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 2935b8e..6a51371 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -33,6 +33,7 @@
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
 import android.content.pm.ManifestDigest;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -52,6 +53,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -59,6 +61,8 @@
 import android.util.ArrayMap;
 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;
@@ -248,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) {
@@ -257,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 {
@@ -1447,6 +1473,62 @@
         return false;
     }
 
+    @Override
+    public KeySet getKeySetByAlias(String packageName, String alias) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(alias);
+        IBinder keySetToken;
+        try {
+            keySetToken = mPM.getKeySetByAlias(packageName, alias);
+        } catch (RemoteException e) {
+            return null;
+        }
+        if (keySetToken == null) {
+            return null;
+        }
+        return new KeySet(keySetToken);
+    }
+
+    @Override
+    public KeySet getSigningKeySet(String packageName) {
+        Preconditions.checkNotNull(packageName);
+        IBinder keySetToken;
+        try {
+            keySetToken = mPM.getSigningKeySet(packageName);
+        } catch (RemoteException e) {
+            return null;
+        }
+        if (keySetToken == null) {
+            return null;
+        }
+        return new KeySet(keySetToken);
+    }
+
+
+    @Override
+    public boolean isSignedBy(String packageName, KeySet ks) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(ks);
+        IBinder keySetToken = ks.getToken();
+        try {
+            return mPM.isPackageSignedByKeySet(packageName, keySetToken);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isSignedByExactly(String packageName, KeySet ks) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(ks);
+        IBinder keySetToken = ks.getToken();
+        try {
+            return mPM.isPackageSignedByKeySetExactly(packageName, keySetToken);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     /**
      * @hide
      */
@@ -1461,7 +1543,7 @@
     }
 
     @Override
-    public PackageInstaller getPackageInstaller() {
+    public PackageInstaller getInstaller() {
         try {
             return new PackageInstaller(this, mPM.getPackageInstaller(), mContext.getPackageName(),
                     mContext.getUserId());
@@ -1470,6 +1552,15 @@
         }
     }
 
+    @Override
+    public boolean isPackageAvailable(String packageName) {
+        try {
+            return mPM.isPackageAvailable(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 395c1b9..e274f09 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -247,7 +247,11 @@
     @GuardedBy("mSync")
     private File mFilesDir;
     @GuardedBy("mSync")
+    private File mNoBackupFilesDir;
+    @GuardedBy("mSync")
     private File mCacheDir;
+    @GuardedBy("mSync")
+    private File mCodeCacheDir;
 
     @GuardedBy("mSync")
     private File[] mExternalObbDirs;
@@ -961,27 +965,42 @@
         return f.delete();
     }
 
+    // Common-path handling of app data dir creation
+    private static File createFilesDirLocked(File file) {
+        if (!file.exists()) {
+            if (!file.mkdirs()) {
+                if (file.exists()) {
+                    // spurious failure; probably racing with another process for this app
+                    return file;
+                }
+                Log.w(TAG, "Unable to create files subdir " + file.getPath());
+                return null;
+            }
+            FileUtils.setPermissions(
+                    file.getPath(),
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                    -1, -1);
+        }
+        return file;
+    }
+
     @Override
     public File getFilesDir() {
         synchronized (mSync) {
             if (mFilesDir == null) {
                 mFilesDir = new File(getDataDirFile(), "files");
             }
-            if (!mFilesDir.exists()) {
-                if(!mFilesDir.mkdirs()) {
-                    if (mFilesDir.exists()) {
-                        // spurious failure; probably racing with another process for this app
-                        return mFilesDir;
-                    }
-                    Log.w(TAG, "Unable to create files directory " + mFilesDir.getPath());
-                    return null;
-                }
-                FileUtils.setPermissions(
-                        mFilesDir.getPath(),
-                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-                        -1, -1);
+            return createFilesDirLocked(mFilesDir);
+        }
+    }
+
+    @Override
+    public File getNoBackupFilesDir() {
+        synchronized (mSync) {
+            if (mNoBackupFilesDir == null) {
+                mNoBackupFilesDir = new File(getDataDirFile(), "no_backup");
             }
-            return mFilesDir;
+            return createFilesDirLocked(mNoBackupFilesDir);
         }
     }
 
@@ -1033,22 +1052,18 @@
             if (mCacheDir == null) {
                 mCacheDir = new File(getDataDirFile(), "cache");
             }
-            if (!mCacheDir.exists()) {
-                if(!mCacheDir.mkdirs()) {
-                    if (mCacheDir.exists()) {
-                        // spurious failure; probably racing with another process for this app
-                        return mCacheDir;
-                    }
-                    Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath());
-                    return null;
-                }
-                FileUtils.setPermissions(
-                        mCacheDir.getPath(),
-                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-                        -1, -1);
-            }
+            return createFilesDirLocked(mCacheDir);
         }
-        return mCacheDir;
+    }
+
+    @Override
+    public File getCodeCacheDir() {
+        synchronized (mSync) {
+            if (mCodeCacheDir == null) {
+                mCodeCacheDir = new File(getDataDirFile(), "code_cache");
+            }
+            return createFilesDirLocked(mCodeCacheDir);
+        }
     }
 
     @Override
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/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index f50c93b..1326064 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -282,7 +282,7 @@
         ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
                 mSharedElementNames);
         setTransitionAlpha(mSharedElements, 1);
-        ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
+        ArrayList<SharedElementOriginalState> originalImageViewState =
                 setSharedElementState(sharedElementState, sharedElementSnapshots);
         requestLayoutForSharedElements();
 
@@ -294,7 +294,7 @@
             startEnterTransition(transition);
         }
 
-        setOriginalImageViewState(originalImageViewState);
+        setOriginalSharedElementState(mSharedElements, originalImageViewState);
 
         if (mResultReceiver != null) {
             // We can't trust that the view will disappear on the same frame that the shared
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/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index 7117111..1b2504e 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -25,7 +25,7 @@
         @Override
         public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
                 String msg) {
-            PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
+            PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode, msg);
         }
     };
 
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/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 886f1a6..e2a86e8 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -247,12 +247,40 @@
             throws IOException;
 
     /**
-     * The default implementation backs up the entirety of the application's "owned"
-     * file system trees to the output.
+     * The application is having its entire file system contents backed up.  {@code data}
+     * points to the backup destination, and the app has the opportunity to choose which
+     * files are to be stored.  To commit a file as part of the backup, call the
+     * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method.  After all file
+     * data is written to the output, the agent returns from this method and the backup
+     * operation concludes.
+     *
+     * <p>Certain parts of the app's data are never backed up even if the app explicitly
+     * sends them to the output:
+     *
+     * <ul>
+     * <li>The contents of the {@link #getCacheDir()} directory</li>
+     * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
+     * <li>The contents of the app's shared library directory</li>
+     * </ul>
+     *
+     * <p>The default implementation of this method backs up the entirety of the
+     * application's "owned" file system trees to the output other than the few exceptions
+     * listed above.  Apps only need to override this method if they need to impose special
+     * limitations on which files are being stored beyond the control that
+     * {@link #getNoBackupFilesDir()} offers.
+     *
+     * @param data A structured wrapper pointing to the backup destination.
+     * @throws IOException
+     *
+     * @see Context#getNoBackupFilesDir()
+     * @see #fullBackupFile(File, FullBackupDataOutput)
+     * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
      */
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
         ApplicationInfo appInfo = getApplicationInfo();
 
+        // Note that we don't need to think about the no_backup dir because it's outside
+        // all of the ones we will be traversing
         String rootDir = new File(appInfo.dataDir).getCanonicalPath();
         String filesDir = getFilesDir().getCanonicalPath();
         String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
@@ -311,6 +339,10 @@
      * to place it with the proper location and permissions on the device where the
      * data is restored.
      *
+     * <p class="note">It is safe to explicitly back up files underneath your application's
+     * {@link #getNoBackupFilesDir()} directory, and they will be restored to that
+     * location correctly.
+     *
      * @param file The file to be backed up.  The file must exist and be readable by
      *     the caller.
      * @param output The destination to which the backed-up file data will be sent.
@@ -319,6 +351,7 @@
         // Look up where all of our various well-defined dir trees live on this device
         String mainDir;
         String filesDir;
+        String nbFilesDir;
         String dbDir;
         String spDir;
         String cacheDir;
@@ -331,6 +364,7 @@
         try {
             mainDir = new File(appInfo.dataDir).getCanonicalPath();
             filesDir = getFilesDir().getCanonicalPath();
+            nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
             dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
             spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
             cacheDir = getCacheDir().getCanonicalPath();
@@ -354,8 +388,10 @@
             return;
         }
 
-        if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) {
-            Log.w(TAG, "lib and cache files are not backed up");
+        if (filePath.startsWith(cacheDir)
+                || filePath.startsWith(libDir)
+                || filePath.startsWith(nbFilesDir)) {
+            Log.w(TAG, "lib, cache, and no_backup files are not backed up");
             return;
         }
 
@@ -508,6 +544,8 @@
                     mode = -1;  // < 0 is a token to skip attempting a chmod()
                 }
             }
+        } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
+            basePath = getNoBackupFilesDir().getCanonicalPath();
         } else {
             // Not a supported location
             Log.i(TAG, "Unrecognized domain " + domain);
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 6ebb6c4..e5b47c6 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -40,6 +40,7 @@
     public static final String OBB_TREE_TOKEN = "obb";
     public static final String ROOT_TREE_TOKEN = "r";
     public static final String DATA_TREE_TOKEN = "f";
+    public static final String NO_BACKUP_TREE_TOKEN = "nb";
     public static final String DATABASE_TREE_TOKEN = "db";
     public static final String SHAREDPREFS_TREE_TOKEN = "sp";
     public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
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 af218eb2..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 535aaa1..d068b1f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -647,6 +647,26 @@
     public abstract File getFilesDir();
 
     /**
+     * Returns the absolute path to the directory on the filesystem similar to
+     * {@link #getFilesDir()}.  The difference is that files placed under this
+     * directory will be excluded from automatic backup to remote storage.  See
+     * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
+     * of the automatic backup mechanism in Android.
+     *
+     * <p>No permissions are required to read or write to the returned path, since this
+     * path is internal storage.
+     *
+     * @return The path of the directory holding application files that will not be
+     *         automatically backed up to remote storage.
+     *
+     * @see #openFileOutput
+     * @see #getFileStreamPath
+     * @see #getDir
+     * @see android.app.backup.BackupAgent
+     */
+    public abstract File getNoBackupFilesDir();
+
+    /**
      * 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
@@ -847,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
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index dbf9122..4e1c4a7 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -200,7 +200,12 @@
     public File getFilesDir() {
         return mBase.getFilesDir();
     }
-    
+
+    @Override
+    public File getNoBackupFilesDir() {
+        return mBase.getNoBackupFilesDir();
+    }
+
     @Override
     public File getExternalFilesDir(String type) {
         return mBase.getExternalFilesDir(type);
@@ -227,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..bc14c1e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1413,6 +1413,21 @@
     public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
 
     /**
+     * Activity Action: Prompt the user to confirm credentials (pin, pattern or password)
+     * for the current user of the device. Launch this activity using
+     * {@link android.app.Activity#startActivityForResult(Intent, int)} and check if the
+     * result is {@link android.app.Activity#RESULT_OK} for a successful response to the
+     * challenge.<p/>
+     * This intent is handled by the system at a high priority and applications cannot intercept
+     * it.<p/>
+     * You can use {@link android.app.KeyguardManager#isKeyguardSecure()} to determine if the user will be
+     * prompted.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CONFIRM_DEVICE_CREDENTIAL = "android.intent.action.CONFIRM_DEVICE_CREDENTIAL";
+
+
+    /**
      * Specify whether the package should be uninstalled for all users.
      * @hide because these should not be part of normal application flow.
      */
@@ -3161,11 +3176,17 @@
 
     /**
      * A CharSequence dialog title to provide to the user when used with a
-     * {@link #ACTION_CHOOSER}.
+     * {@link #ACTION_CHOOSER} or {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
      */
     public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
 
     /**
+     * A CharSequence description to provide to the user when used with
+     * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
+     */
+    public static final String EXTRA_DETAILS = "android.intent.extra.DETAILS";
+
+    /**
      * A Parcelable[] of {@link Intent} or
      * {@link android.content.pm.LabeledIntent} objects as set with
      * {@link #putExtra(String, Parcelable[])} of additional activities to place
@@ -3809,17 +3830,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 58d3526..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
@@ -429,4 +435,9 @@
 
     boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
     boolean getBlockUninstallForUser(String packageName, int userId);
+
+    IBinder getKeySetByAlias(String packageName, String alias);
+    IBinder getSigningKeySet(String packageName);
+    boolean isPackageSignedByKeySet(String packageName, IBinder ks);
+    boolean isPackageSignedByKeySetExactly(String packageName, IBinder ks);
 }
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
index 45606d1..3336727 100644
--- a/core/java/android/content/pm/InstallSessionInfo.java
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -20,15 +20,25 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/** {@hide} */
+/**
+ * Details for an active install session.
+ */
 public class InstallSessionInfo implements Parcelable {
+
+    /** {@hide} */
     public int sessionId;
+    /** {@hide} */
     public String installerPackageName;
+    /** {@hide} */
     public int progress;
 
-    public boolean fullInstall;
+    /** {@hide} */
+    public int mode;
+    /** {@hide} */
     public String packageName;
+    /** {@hide} */
     public Bitmap icon;
+    /** {@hide} */
     public CharSequence title;
 
     /** {@hide} */
@@ -41,12 +51,62 @@
         installerPackageName = source.readString();
         progress = source.readInt();
 
-        fullInstall = source.readInt() != 0;
+        mode = source.readInt();
         packageName = source.readString();
         icon = source.readParcelable(null);
         title = source.readString();
     }
 
+    /**
+     * Return the ID for this session.
+     */
+    public int getSessionId() {
+        return sessionId;
+    }
+
+    /**
+     * Return the package name of the app that owns this session.
+     */
+    public String getInstallerPackageName() {
+        return installerPackageName;
+    }
+
+    /**
+     * Return current overall progress of this session, between 0 and 100.
+     * <p>
+     * Note that this progress may not directly correspond to the value reported
+     * by {@link PackageInstaller.Session#setProgress(int)}, as the system may
+     * carve out a portion of the overall progress to represent its own internal
+     * installation work.
+     */
+    public int getProgress() {
+        return progress;
+    }
+
+    /**
+     * Return the package name this session is working with. May be {@code null}
+     * if unknown.
+     */
+    public String getPackageName() {
+        return packageName;
+    }
+
+    /**
+     * Return an icon representing the app being installed. May be {@code null}
+     * if unavailable.
+     */
+    public Bitmap getIcon() {
+        return icon;
+    }
+
+    /**
+     * Return a title representing the app being installed. May be {@code null}
+     * if unavailable.
+     */
+    public CharSequence getTitle() {
+        return title;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -58,7 +118,7 @@
         dest.writeString(installerPackageName);
         dest.writeInt(progress);
 
-        dest.writeInt(fullInstall ? 1 : 0);
+        dest.writeInt(mode);
         dest.writeString(packageName);
         dest.writeParcelable(icon, flags);
         dest.writeString(title != null ? title.toString() : null);
diff --git a/core/java/android/content/pm/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java
index 43e3314..e039699 100644
--- a/core/java/android/content/pm/InstallSessionParams.java
+++ b/core/java/android/content/pm/InstallSessionParams.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Parcel;
@@ -23,13 +24,22 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 
-/** {@hide} */
+/**
+ * Parameters for creating a new {@link PackageInstaller.Session}.
+ */
 public class InstallSessionParams implements Parcelable {
 
     // TODO: extend to support remaining VerificationParams
 
     /** {@hide} */
-    public boolean fullInstall;
+    public static final int MODE_INVALID = 0;
+    /** {@hide} */
+    public static final int MODE_FULL_INSTALL = 1;
+    /** {@hide} */
+    public static final int MODE_INHERIT_EXISTING = 2;
+
+    /** {@hide} */
+    public int mode = MODE_INVALID;
     /** {@hide} */
     public int installFlags;
     /** {@hide} */
@@ -58,7 +68,7 @@
 
     /** {@hide} */
     public InstallSessionParams(Parcel source) {
-        fullInstall = source.readInt() != 0;
+        mode = source.readInt();
         installFlags = source.readInt();
         installLocation = source.readInt();
         signatures = (Signature[]) source.readParcelableArray(null);
@@ -72,53 +82,108 @@
         abiOverride = source.readString();
     }
 
-    public void setFullInstall(boolean fullInstall) {
-        this.fullInstall = fullInstall;
+    /**
+     * Set session mode indicating that it should fully replace any existing
+     * APKs for this application.
+     */
+    public void setModeFullInstall() {
+        this.mode = MODE_FULL_INSTALL;
     }
 
-    public void setInstallFlags(int installFlags) {
-        this.installFlags = installFlags;
+    /**
+     * Set session mode indicating that it should inherit any existing APKs for
+     * this application, unless they are explicitly overridden (by split name)
+     * in the session.
+     */
+    public void setModeInheritExisting() {
+        this.mode = MODE_INHERIT_EXISTING;
     }
 
+    /**
+     * Provide value of {@link PackageInfo#installLocation}, which may be used
+     * to determine where the app will be staged. Defaults to
+     * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
+     */
     public void setInstallLocation(int installLocation) {
         this.installLocation = installLocation;
     }
 
+    /**
+     * Optionally provide the required value of {@link PackageInfo#signatures}.
+     * This can be used to assert that all staged APKs have been signed with
+     * this set of specific certificates. Regardless of this value, all APKs in
+     * the application must have the same signing certificates.
+     */
     public void setSignatures(Signature[] signatures) {
         this.signatures = signatures;
     }
 
+    /**
+     * Indicate the expected growth in disk usage resulting from this session.
+     * This may be used to ensure enough disk space exists before proceeding, or
+     * to estimate container size for installations living on external storage.
+     * <p>
+     * This value should only reflect APK sizes.
+     */
     public void setDeltaSize(long deltaSize) {
         this.deltaSize = deltaSize;
     }
 
+    /**
+     * Set the maximum progress for this session, used for normalization
+     * purposes.
+     *
+     * @see PackageInstaller.Session#setProgress(int)
+     */
     public void setProgressMax(int progressMax) {
         this.progressMax = progressMax;
     }
 
+    /**
+     * Optionally set the package name this session will be working with. It's
+     * strongly recommended that you provide this value when known.
+     */
     public void setPackageName(String packageName) {
         this.packageName = packageName;
     }
 
+    /**
+     * Optionally set an icon representing the app being installed.
+     */
     public void setIcon(Bitmap icon) {
         this.icon = icon;
     }
 
+    /**
+     * Optionally set a title representing the app being installed.
+     */
     public void setTitle(CharSequence title) {
         this.title = title;
     }
 
+    /**
+     * Optionally set the URI where this package was downloaded from. Used for
+     * verification purposes.
+     *
+     * @see Intent#EXTRA_ORIGINATING_URI
+     */
     public void setOriginatingUri(Uri originatingUri) {
         this.originatingUri = originatingUri;
     }
 
+    /**
+     * Optionally set the URI that referred you to install this package. Used
+     * for verification purposes.
+     *
+     * @see Intent#EXTRA_REFERRER
+     */
     public void setReferrerUri(Uri referrerUri) {
         this.referrerUri = referrerUri;
     }
 
     /** {@hide} */
     public void dump(IndentingPrintWriter pw) {
-        pw.printPair("fullInstall", fullInstall);
+        pw.printPair("mode", mode);
         pw.printHexPair("installFlags", installFlags);
         pw.printPair("installLocation", installLocation);
         pw.printPair("signatures", (signatures != null));
@@ -140,7 +205,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(fullInstall ? 1 : 0);
+        dest.writeInt(mode);
         dest.writeInt(installFlags);
         dest.writeInt(installLocation);
         dest.writeParcelableArray(signatures, flags);
diff --git a/core/java/android/content/pm/KeySet.java b/core/java/android/content/pm/KeySet.java
index 0ef09a4..fcdaa18 100644
--- a/core/java/android/content/pm/KeySet.java
+++ b/core/java/android/content/pm/KeySet.java
@@ -16,19 +16,36 @@
 
 package android.content.pm;
 
-import android.os.Binder;
+import android.os.IBinder;
 
-/** @hide */
+/**
+ * Represents a {@code KeySet} that has been declared in the AndroidManifest.xml
+ * file for the application.  A {@code KeySet} can be used explicitly to
+ * represent a trust relationship with other applications on the device.
+ */
 public class KeySet {
 
-    private Binder token;
+    private IBinder token;
 
     /** @hide */
-    public KeySet(Binder token) {
+    public KeySet(IBinder token) {
+        if (token == null) {
+            throw new NullPointerException("null value for KeySet IBinder token");
+        }
         this.token = token;
     }
 
-    Binder getToken() {
+    /** @hide */
+    public IBinder getToken() {
         return token;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof KeySet) {
+            KeySet ks = (KeySet) o;
+            return token == ks.token;
+        }
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index ef0c4d5..49ffef2 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -31,6 +31,11 @@
     public String packageName;
 
     /**
+     * The names of any installed split APKs for this package.
+     */
+    public String[] splitNames;
+
+    /**
      * The version number of this package, as specified by the &lt;manifest&gt;
      * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode}
      * attribute.
@@ -180,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;
 
@@ -190,24 +197,25 @@
      * @hide
      */
     public static final int INSTALL_LOCATION_UNSPECIFIED = -1;
+
     /**
-     * Constant corresponding to <code>auto</code> in
-     * the {@link android.R.attr#installLocation} attribute.
-     * @hide
+     * Constant corresponding to <code>auto</code> in the
+     * {@link android.R.attr#installLocation} attribute.
      */
     public static final int INSTALL_LOCATION_AUTO = 0;
+
     /**
-     * Constant corresponding to <code>internalOnly</code> in
-     * the {@link android.R.attr#installLocation} attribute.
-     * @hide
+     * Constant corresponding to <code>internalOnly</code> in the
+     * {@link android.R.attr#installLocation} attribute.
      */
     public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1;
+
     /**
-     * Constant corresponding to <code>preferExternal</code> in
-     * the {@link android.R.attr#installLocation} attribute.
-     * @hide
+     * Constant corresponding to <code>preferExternal</code> in the
+     * {@link android.R.attr#installLocation} attribute.
      */
     public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;
+
     /**
      * Flag for {@link #requiredForProfile}
      * The application will always be installed for a restricted profile.
@@ -222,12 +230,10 @@
     public static final int MANAGED_PROFILE = 2;
 
     /**
-     * The install location requested by the activity.  From the
+     * The install location requested by the package. From the
      * {@link android.R.attr#installLocation} attribute, one of
-     * {@link #INSTALL_LOCATION_AUTO},
-     * {@link #INSTALL_LOCATION_INTERNAL_ONLY},
+     * {@link #INSTALL_LOCATION_AUTO}, {@link #INSTALL_LOCATION_INTERNAL_ONLY},
      * {@link #INSTALL_LOCATION_PREFER_EXTERNAL}
-     * @hide
      */
     public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
 
@@ -269,6 +275,7 @@
 
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         dest.writeString(packageName);
+        dest.writeStringArray(splitNames);
         dest.writeInt(versionCode);
         dest.writeString(versionName);
         dest.writeString(sharedUserId);
@@ -314,6 +321,7 @@
 
     private PackageInfo(Parcel source) {
         packageName = source.readString();
+        splitNames = source.readStringArray();
         versionCode = source.readInt();
         versionName = source.readString();
         sharedUserId = source.readString();
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 401be06..df82d26 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,7 +30,31 @@
 import java.io.OutputStream;
 import java.util.List;
 
-/** {@hide} */
+/**
+ * Offers the ability to install, upgrade, and remove applications on the
+ * device. This includes support for apps packaged either as a single
+ * "monolithic" APK, or apps packaged as multiple "split" APKs.
+ * <p>
+ * An app is delivered for installation through a
+ * {@link PackageInstaller.Session}, which any app can create. Once the session
+ * is created, the installer can stream one or more APKs into place until it
+ * decides to either commit or destroy the session. Committing may require user
+ * intervention to complete the installation.
+ * <p>
+ * Sessions can install brand new apps, upgrade existing apps, or add new splits
+ * into an existing app.
+ * <p>
+ * 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 APKs must have unique split names.
+ * <li>All installations must contain a single base APK.
+ * </ul>
+ */
 public class PackageInstaller {
     private final PackageManager mPm;
     private final IPackageInstaller mInstaller;
@@ -46,16 +70,18 @@
         mUserId = userId;
     }
 
+    /**
+     * Quickly test if the given package is already available on the device.
+     * This is typically used in multi-user scenarios where another user on the
+     * device has already installed the package.
+     *
+     * @hide
+     */
     public boolean isPackageAvailable(String packageName) {
-        try {
-            final ApplicationInfo info = mPm.getApplicationInfo(packageName,
-                    PackageManager.GET_UNINSTALLED_PACKAGES);
-            return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
-        } catch (NameNotFoundException e) {
-            return false;
-        }
+        return mPm.isPackageAvailable(packageName);
     }
 
+    /** {@hide} */
     public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
         int returnCode;
         try {
@@ -66,6 +92,18 @@
         observer.packageInstalled(packageName, null, returnCode);
     }
 
+    /**
+     * Create a new session using the given parameters, returning a unique ID
+     * that represents the session. Once created, the session can be opened
+     * multiple times across multiple device boots.
+     * <p>
+     * The system may automatically destroy sessions that have not been
+     * finalized (either committed or abandoned) within a reasonable period of
+     * time, typically on the order of a day.
+     *
+     * @throws IOException if parameters were unsatisfiable, such as lack of
+     *             disk space or unavailable media.
+     */
     public int createSession(InstallSessionParams params) throws IOException {
         try {
             return mInstaller.createSession(mInstallerPackageName, params, mUserId);
@@ -77,6 +115,9 @@
         }
     }
 
+    /**
+     * Open an existing session to actively perform work.
+     */
     public Session openSession(int sessionId) {
         try {
             return new Session(mInstaller.openSession(sessionId));
@@ -85,7 +126,10 @@
         }
     }
 
-    public List<InstallSessionInfo> getSessions() {
+    /**
+     * Return list of all active install sessions on the device.
+     */
+    public List<InstallSessionInfo> getActiveSessions() {
         // TODO: filter based on caller
         // TODO: let launcher app see all active sessions
         try {
@@ -95,6 +139,11 @@
         }
     }
 
+    /**
+     * Uninstall the given package, removing it completely from the device. This
+     * method is only available to the current "installer of record" for the
+     * package.
+     */
     public void uninstall(String packageName, UninstallResultCallback callback) {
         try {
             mInstaller.uninstall(packageName, 0,
@@ -104,6 +153,11 @@
         }
     }
 
+    /**
+     * Uninstall only a specific split from the given package.
+     *
+     * @hide
+     */
     public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
         try {
             mInstaller.uninstallSplit(packageName, splitName, 0,
@@ -113,6 +167,9 @@
         }
     }
 
+    /**
+     * Events for observing session lifecycle.
+     */
     public static abstract class SessionObserver {
         private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() {
             @Override
@@ -127,7 +184,7 @@
 
             @Override
             public void onSessionFinished(int sessionId, boolean success) {
-                SessionObserver.this.onFinished(sessionId, success);
+                SessionObserver.this.onFinalized(sessionId, success);
             }
         };
 
@@ -136,11 +193,30 @@
             return mBinder;
         }
 
+        /**
+         * New session has been created.
+         */
         public abstract void onCreated(InstallSessionInfo info);
+
+        /**
+         * Progress for given session has been updated.
+         * <p>
+         * Note that this progress may not directly correspond to the value
+         * reported by {@link PackageInstaller.Session#setProgress(int)}, as the
+         * system may carve out a portion of the overall progress to represent
+         * its own internal installation work.
+         */
         public abstract void onProgress(int sessionId, int progress);
-        public abstract void onFinished(int sessionId, boolean success);
+
+        /**
+         * Session has been finalized, either with success or failure.
+         */
+        public abstract void onFinalized(int sessionId, boolean success);
     }
 
+    /**
+     * Register to watch for session lifecycle events.
+     */
     public void registerSessionObserver(SessionObserver observer) {
         try {
             mInstaller.registerObserver(observer.getBinder(), mUserId);
@@ -149,6 +225,9 @@
         }
     }
 
+    /**
+     * Unregister an existing observer.
+     */
     public void unregisterSessionObserver(SessionObserver observer) {
         try {
             mInstaller.unregisterObserver(observer.getBinder(), mUserId);
@@ -177,6 +256,10 @@
             mSession = session;
         }
 
+        /**
+         * Set current progress. Valid values are anywhere between 0 and
+         * {@link InstallSessionParams#setProgressMax(int)}.
+         */
         public void setProgress(int progress) {
             try {
                 mSession.setClientProgress(progress);
@@ -197,7 +280,7 @@
         /**
          * Open an APK file for writing, starting at the given offset. You can
          * then stream data into the file, periodically calling
-         * {@link OutputStream#flush()} to ensure bytes have been written to
+         * {@link #fsync(OutputStream)} to ensure bytes have been written to
          * disk.
          */
         public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes)
@@ -214,6 +297,11 @@
             }
         }
 
+        /**
+         * Ensure that any outstanding data for given stream has been committed
+         * to disk. This is only valid for streams returned from
+         * {@link #openWrite(String, long, long)}.
+         */
         public void fsync(OutputStream out) throws IOException {
             if (out instanceof FileBridge.FileBridgeOutputStream) {
                 ((FileBridge.FileBridgeOutputStream) out).fsync();
@@ -222,6 +310,15 @@
             }
         }
 
+        /**
+         * Attempt to commit everything staged in this session. This may require
+         * user intervention, and so it may not happen immediately. The final
+         * result of the commit will be reported through the given callback.
+         * <p>
+         * Once this method is called, no additional mutations may be performed
+         * on the session. If the device reboots before the session has been
+         * finalized, you may commit the session again.
+         */
         public void commit(CommitResultCallback callback) {
             try {
                 mSession.install(new CommitResultCallbackDelegate(callback).getBinder());
@@ -230,11 +327,18 @@
             }
         }
 
+        /**
+         * Release this session object. You can open the session again if it
+         * hasn't been finalized.
+         */
         @Override
         public void close() {
             // No resources to release at the moment
         }
 
+        /**
+         * Completely destroy this session, rendering it invalid.
+         */
         public void destroy() {
             try {
                 mSession.destroy();
@@ -244,11 +348,15 @@
         }
     }
 
+    /**
+     * Final result of an uninstall request.
+     */
     public static abstract class UninstallResultCallback {
         public abstract void onSuccess();
         public abstract void onFailure(String msg);
     }
 
+    /** {@hide} */
     private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
         private final UninstallResultCallback target;
 
@@ -271,8 +379,18 @@
         }
     }
 
+    /**
+     * Final result of a session commit request.
+     */
     public static abstract class CommitResultCallback {
         public abstract void onSuccess();
+
+        /**
+         * Generic failure occurred. You can override methods (such as
+         * {@link #onFailureInvalid(String)}) to handle more specific categories
+         * of failure. By default, those specific categories all flow into this
+         * generic failure.
+         */
         public abstract void onFailure(String msg);
 
         /**
@@ -286,12 +404,16 @@
         }
 
         /**
-         * This install session conflicts (or is inconsistent with) with
-         * another package already installed on the device. For example, an
-         * existing permission, incompatible certificates, etc. The user may
-         * be able to uninstall another app to fix the issue.
+         * This install session conflicts (or is inconsistent with) with another
+         * package already installed on the device. For example, an existing
+         * permission, incompatible certificates, etc. The user may be able to
+         * uninstall another app to fix the issue.
+         *
+         * @param otherPackageName if one specific package was identified as the
+         *            cause of the conflict, it's named here. If unknown, or
+         *            multiple packages, this may be {@code null}.
          */
-        public void onFailureConflict(String msg, String packageName) {
+        public void onFailureConflict(String msg, String otherPackageName) {
             onFailure(msg);
         }
 
@@ -317,6 +439,7 @@
         }
     }
 
+    /** {@hide} */
     private static class CommitResultCallbackDelegate extends PackageInstallObserver {
         private final CommitResultCallback target;
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 03d4701..37df29a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1167,6 +1167,22 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device includes a relative humidity sensor.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SENSOR_RELATIVE_HUMIDITY =
+            "android.hardware.sensor.relative_humidity";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device includes an ambient temperature sensor.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE =
+            "android.hardware.sensor.ambient_temperature";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device has a telephony radio with data
      * communication support.
      */
@@ -1623,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);
 
@@ -3602,6 +3616,33 @@
     public abstract boolean isSafeMode();
 
     /**
+     * Return the {@link KeySet} associated with the String alias for this
+     * application.
+     *
+     * @param alias The alias for a given {@link KeySet} as defined in the
+     *        application's AndroidManifest.xml.
+     */
+    public abstract KeySet getKeySetByAlias(String packageName, String alias);
+
+    /** Return the signing {@link KeySet} for this application. */
+    public abstract KeySet getSigningKeySet(String packageName);
+
+    /**
+     * Return whether the package denoted by packageName has been signed by all
+     * of the keys specified by the {@link KeySet} ks.  This will return true if
+     * the package has been signed by additional keys (a superset) as well.
+     * Compare to {@link #isSignedByExactly(String packageName, KeySet ks)}.
+     */
+    public abstract boolean isSignedBy(String packageName, KeySet ks);
+
+    /**
+     * Return whether the package denoted by packageName has been signed by all
+     * of, and only, the keys specified by the {@link KeySet} ks. Compare to
+     * {@link #isSignedBy(String packageName, KeySet ks)}.
+     */
+    public abstract boolean isSignedByExactly(String packageName, KeySet ks);
+
+    /**
      * Attempts to move package resources from internal to external media or vice versa.
      * Since this may take a little while, the result will
      * be posted back to the given observer.   This call may fail if the calling context
@@ -3629,8 +3670,11 @@
      */
     public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
 
-    /** {@hide} */
-    public abstract PackageInstaller getPackageInstaller();
+    /**
+     * Return interface that offers the ability to install, upgrade, and remove
+     * applications on the device.
+     */
+    public abstract PackageInstaller getInstaller();
 
     /**
      * Returns the data directory for a particular user and package, given the uid of the package.
@@ -3676,4 +3720,7 @@
      * @hide
      */
     public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo);
+
+    /** {@hide} */
+    public abstract boolean isPackageAvailable(String packageName);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d92a90d..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;
     }
@@ -380,6 +403,7 @@
         }
         PackageInfo pi = new PackageInfo();
         pi.packageName = p.packageName;
+        pi.splitNames = p.splitNames;
         pi.versionCode = p.mVersionCode;
         pi.versionName = p.mVersionName;
         pi.sharedUserId = p.mSharedUserId;
@@ -400,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);
@@ -590,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()) {
@@ -676,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);
@@ -696,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) {
@@ -704,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);
             }
         }
 
@@ -729,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;
     }
 
     /**
@@ -1000,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);
@@ -1016,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.
@@ -1035,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);
         }
     }
 
@@ -1117,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")) {
@@ -1128,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) {
@@ -1154,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);
     }
 
     /**
@@ -1172,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;
@@ -1194,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;
@@ -1282,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")) {
@@ -1354,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);
 
@@ -1377,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);
@@ -2196,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;
@@ -2316,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);
 
@@ -2441,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;
@@ -2507,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);
                     }
                 }
 
@@ -2532,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);
                         }
                     }
                 }
@@ -3865,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();
 
@@ -3916,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;
@@ -3933,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;
@@ -3990,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 cf462cd..eadfa73 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -133,7 +133,11 @@
  * <p>For more information about using cameras, read the
  * <a href="{@docRoot}guide/topics/media/camera.html">Camera</a> developer guide.</p>
  * </div>
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ *             applications.
  */
+@Deprecated
 public class Camera {
     private static final String TAG = "Camera";
 
@@ -247,7 +251,11 @@
 
     /**
      * Information about a camera
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public static class CameraInfo {
         /**
          * The facing of the camera is opposite to that of the screen.
@@ -675,7 +683,11 @@
      * @see #setOneShotPreviewCallback(Camera.PreviewCallback)
      * @see #setPreviewCallbackWithBuffer(Camera.PreviewCallback)
      * @see #startPreview()
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface PreviewCallback
     {
         /**
@@ -1175,7 +1187,10 @@
      * manifest element.</p>
      *
      * @see #autoFocus(AutoFocusCallback)
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface AutoFocusCallback
     {
         /**
@@ -1286,7 +1301,11 @@
      * Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link
      * Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show
      * autofocus animation based on this.</p>
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface AutoFocusMoveCallback
     {
         /**
@@ -1314,7 +1333,11 @@
      * Callback interface used to signal the moment of actual image capture.
      *
      * @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface ShutterCallback
     {
         /**
@@ -1331,7 +1354,11 @@
      * Callback interface used to supply image data from a photo capture.
      *
      * @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface PictureCallback {
         /**
          * Called when image data is available after a picture is taken.
@@ -1538,7 +1565,11 @@
      *
      * @see #setZoomChangeListener(OnZoomChangeListener)
      * @see #startSmoothZoom(int)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface OnZoomChangeListener
     {
         /**
@@ -1568,7 +1599,10 @@
     /**
      * Callback interface for face detected in the preview frame.
      *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface FaceDetectionListener
     {
         /**
@@ -1652,7 +1686,10 @@
      * list of face objects for use in focusing and metering.</p>
      *
      * @see FaceDetectionListener
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public static class Face {
         /**
          * Create an empty face.
@@ -1766,7 +1803,11 @@
      * Callback interface for camera error notification.
      *
      * @see #setErrorCallback(ErrorCallback)
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public interface ErrorCallback
     {
         /**
@@ -1864,7 +1905,10 @@
 
     /**
      * Image size (width and height dimensions).
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public class Size {
         /**
          * Sets the dimensions for pictures.
@@ -1931,7 +1975,11 @@
      * @see Parameters#setMeteringAreas(List)
      * @see Parameters#getMeteringAreas()
      * @see Parameters#getMaxNumMeteringAreas()
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public static class Area {
         /**
          * Create an area with specified rectangle and weight.
@@ -2005,7 +2053,11 @@
      * calling {@link Camera.Parameters#setColorEffect(String)}. If the
      * camera does not support color effects,
      * {@link Camera.Parameters#getSupportedColorEffects()} will return null.
+     *
+     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+     *             applications.
      */
+    @Deprecated
     public class Parameters {
         // Parameter keys to communicate with the camera driver.
         private static final String KEY_PREVIEW_SIZE = "preview-size";
@@ -2371,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
@@ -2960,7 +3025,6 @@
             case ImageFormat.YV12:      return PIXEL_FORMAT_YUV420P;
             case ImageFormat.RGB_565:   return PIXEL_FORMAT_RGB565;
             case ImageFormat.JPEG:      return PIXEL_FORMAT_JPEG;
-            case ImageFormat.BAYER_RGGB: return PIXEL_FORMAT_BAYER_RGGB;
             default:                    return null;
             }
         }
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/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index e9793c4..5f29e5c 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2.legacy;
 
 import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
@@ -463,6 +464,24 @@
         return ids.contains(id);
     }
 
+    static void setSurfaceOrientation(Surface surface, int facing, int sensorOrientation)
+            throws BufferQueueAbandonedException {
+        checkNotNull(surface);
+        LegacyExceptionUtils.throwOnError(nativeSetSurfaceOrientation(surface, facing,
+                sensorOrientation));
+    }
+
+    static Size getTextureSize(SurfaceTexture surfaceTexture)
+            throws BufferQueueAbandonedException {
+        checkNotNull(surfaceTexture);
+
+        int[] dimens = new int[2];
+        LegacyExceptionUtils.throwOnError(nativeDetectTextureDimens(surfaceTexture,
+                /*out*/dimens));
+
+        return new Size(dimens[0], dimens[1]);
+    }
+
     private static native int nativeDetectSurfaceType(Surface surface);
 
     private static native int nativeDetectSurfaceDimens(Surface surface,
@@ -479,4 +498,11 @@
     private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
 
     private static native long nativeGetSurfaceId(Surface surface);
+
+    private static native int nativeSetSurfaceOrientation(Surface surface, int facing,
+                                                             int sensorOrientation);
+
+    private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture,
+            /*out*/int[/*2*/] dimens);
+
 }
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 e6d84c5..cc7a90eb 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) {
@@ -292,10 +293,13 @@
         mInFlightPreview = null;
         mInFlightJpeg = null;
 
+        int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
+        int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
         if (outputs != null) {
             for (Surface s : outputs) {
                 try {
                     int format = LegacyCameraDevice.detectSurfaceType(s);
+                    LegacyCameraDevice.setSurfaceOrientation(s, facing, orientation);
                     switch (format) {
                         case CameraMetadataNative.NATIVE_JPEG_FORMAT:
                             mCallbackOutputs.add(s);
@@ -392,6 +396,7 @@
             mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
         }
 
+        mCamera.setParameters(mParams);
         // TODO: configure the JPEG surface with some arbitrary size
         // using LegacyCameraDevice.nativeConfigureSurface
     }
@@ -527,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) {
@@ -537,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;
@@ -550,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();
@@ -570,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
@@ -584,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 {
@@ -613,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/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index daa64c0..fdf9ba0 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -1,18 +1,18 @@
 /*
-* Copyright (C) 2014 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package android.hardware.camera2.legacy;
 
 import android.graphics.ImageFormat;
@@ -98,14 +98,14 @@
      */
     private static final String VERTEX_SHADER =
             "uniform mat4 uMVPMatrix;\n" +
-                    "uniform mat4 uSTMatrix;\n" +
-                    "attribute vec4 aPosition;\n" +
-                    "attribute vec4 aTextureCoord;\n" +
-                    "varying vec2 vTextureCoord;\n" +
-                    "void main() {\n" +
-                    "  gl_Position = uMVPMatrix * aPosition;\n" +
-                    "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
-                    "}\n";
+            "uniform mat4 uSTMatrix;\n" +
+            "attribute vec4 aPosition;\n" +
+            "attribute vec4 aTextureCoord;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "void main() {\n" +
+            "  gl_Position = uMVPMatrix * aPosition;\n" +
+            "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+            "}\n";
 
     /**
      * This fragment shader simply draws the color in the 2D texture at
@@ -113,12 +113,12 @@
      */
     private static final String FRAGMENT_SHADER =
             "#extension GL_OES_EGL_image_external : require\n" +
-                    "precision mediump float;\n" +
-                    "varying vec2 vTextureCoord;\n" +
-                    "uniform samplerExternalOES sTexture;\n" +
-                    "void main() {\n" +
-                    "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
-                    "}\n";
+            "precision mediump float;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "uniform samplerExternalOES sTexture;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+            "}\n";
 
     private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
     private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
@@ -189,12 +189,56 @@
         return program;
     }
 
-    private void drawFrame(SurfaceTexture st) {
+    private void drawFrame(SurfaceTexture st, int width, int height) {
         checkGlError("onDrawFrame start");
         st.getTransformMatrix(mSTMatrix);
 
+        Size dimens;
+        try {
+            dimens = LegacyCameraDevice.getTextureSize(st);
+        } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+            // Should never hit this.
+            throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
+        }
+
+        Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
+
+        float texWidth = dimens.getWidth();
+        float texHeight = dimens.getHeight();
+
+        if (texWidth <= 0 || texHeight <= 0) {
+            throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
+        }
+
+        // Find largest scaling factor from the intermediate texture dimension to the
+        // output surface dimension.  Scaling the intermediate texture by this allows
+        // us to letterbox/pillerbox the output surface into the intermediate texture.
+        float widthRatio = width / texWidth;
+        float heightRatio = height / texHeight;
+        float actual = (widthRatio < heightRatio) ? heightRatio : widthRatio;
+
         if (DEBUG) {
-            GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+            Log.d(TAG, "Scaling factor " + actual + " used for " + width + "x" + height +
+                    " surface, intermediate buffer size is " + texWidth + "x" + texHeight);
+        }
+
+        // Set the viewport height and width to be the scaled intermediate texture dimensions.
+        int viewportW = (int) (actual * texWidth);
+        int viewportH = (int) (actual * texHeight);
+
+        // Set the offset of the viewport so that the output surface is centered in the viewport.
+        float dx = (width - viewportW) / 2f;
+        float dy = (height - viewportH) / 2f;
+
+        if (DEBUG) {
+            Log.d(TAG, "Translation " + dx + "," + dy + " used for " + width + "x" + height +
+                    " surface");
+        }
+
+        GLES20.glViewport((int) dx, (int) dy, viewportW, viewportH);
+
+        if (DEBUG) {
+            GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
             GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
         }
 
@@ -218,7 +262,6 @@
         GLES20.glEnableVertexAttribArray(maTextureHandle);
         checkGlError("glEnableVertexAttribArray maTextureHandle");
 
-        Matrix.setIdentityM(mMVPMatrix, 0);
         GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
                 /*offset*/ 0);
         GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
@@ -589,18 +632,19 @@
         for (EGLSurfaceHolder holder : mSurfaces) {
             if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
                 makeCurrent(holder.eglSurface);
-                drawFrame(mSurfaceTexture);
+                drawFrame(mSurfaceTexture, holder.width, holder.height);
                 swapBuffers(holder.eglSurface);
             }
         }
         for (EGLSurfaceHolder holder : mConversionSurfaces) {
             if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
                 makeCurrent(holder.eglSurface);
-                drawFrame(mSurfaceTexture);
+                drawFrame(mSurfaceTexture, holder.width, holder.height);
                 mPBufferPixels.clear();
-                GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA,
-                        GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
+                GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
+                        GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
                 checkGlError("glReadPixels");
+
                 try {
                     int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
                     LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
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/package.html b/core/java/android/hardware/camera2/package.html
index ef0d7bd..719c2f6 100644
--- a/core/java/android/hardware/camera2/package.html
+++ b/core/java/android/hardware/camera2/package.html
@@ -58,16 +58,16 @@
 <p>Generally, camera preview images are sent to {@link
 android.view.SurfaceView} or {@link android.view.TextureView} (via its
 {@link android.graphics.SurfaceTexture}). Capture of JPEG images or
-RAW buffers for {@link android.hardware.camera2.DngCreator} can be done
-with {@link android.media.ImageReader} with the
-{android.graphics.ImageFormat#JPEG} and
-{android.graphics.ImageFormat#RAW_SENSOR} formats.  Application-driven
+RAW buffers for {@link android.hardware.camera2.DngCreator} can be
+done with {@link android.media.ImageReader} with the {@link
+android.graphics.ImageFormat#JPEG} and {@link
+android.graphics.ImageFormat#RAW_SENSOR} formats.  Application-driven
 processing of camera data in RenderScript, OpenGL ES, or directly in
 managed or native code is best done through {@link
 android.renderscript.Allocation} with a YUV {@link
 android.renderscript.Type}, {@link android.graphics.SurfaceTexture},
-and {@link android.media.ImageReader} with a
-{android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
+and {@link android.media.ImageReader} with a {@link
+android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
 
 <p>The application then needs to construct a {@link
 android.hardware.camera2.CaptureRequest}, which defines all the
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/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 1557ab0..c5aee7b 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -230,7 +230,7 @@
      *
      * @see #EXTRA_PRINT_JOB_INFO
      */
-    public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO";
+    public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.EXTRA_PRINTER_INFO";
 
     private Handler mHandler;
 
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 07397973..9f1279a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -266,6 +266,21 @@
             "android.settings.WIFI_DISPLAY_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of
+     * {@link android.media.routing.MediaRouteService media route providers}.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CAST_SETTINGS =
+            "android.settings.CAST_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of date and time.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -809,6 +824,15 @@
     public static final String ACTION_BATTERY_SAVER_SETTINGS
             = "android.settings.BATTERY_SAVER_SETTINGS";
 
+    /**
+     * Activity Action: Show Home selection settings. If there are multiple activities
+     * that can satisfy the {@link Intent#CATEGORY_HOME} intent, this screen allows you
+     * to pick your preferred activity.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_HOME_SETTINGS
+            = "android.settings.HOME_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
@@ -2472,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/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 6787fd0..d71ad03 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -188,6 +188,12 @@
          */
         public static final String MIME_TYPE = "mime_type";
         /**
+         * The transcription of the voicemail entry. This will only be populated if the voicemail
+         * entry has a valid transcription.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TRANSCRIPTION = "transcription";
+        /**
          * Path to the media content file. Internal only field.
          * @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/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 fb8ce15..e2ebf6e 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;
@@ -187,12 +190,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;
@@ -204,7 +212,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/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7fab808..b554548 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -461,6 +461,11 @@
                     }
                 }
 
+                // Compute surface insets required to draw at specified Z value.
+                // TODO: Use real shadow insets for a constant max Z.
+                final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
+                attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+
                 CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
                 mTranslator = compatibilityInfo.getTranslator();
                 mDisplayAdjustments.setActivityToken(attrs.token);
@@ -1713,8 +1718,8 @@
                 if (hwInitialized ||
                         mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
                         mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
-                    final Rect shadowInsets = params != null ? params.shadowInsets : null;
-                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets);
+                    final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
+                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, surfaceInsets);
                     if (!hwInitialized) {
                         mAttachInfo.mHardwareRenderer.invalidate(mSurface);
                         mFullRedrawNeeded = true;
@@ -2371,7 +2376,7 @@
         }
 
         final WindowManager.LayoutParams params = mWindowAttributes;
-        final Rect surfaceInsets = params != null ? params.shadowInsets : null;
+        final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
         boolean animating = mScroller != null && mScroller.computeScrollOffset();
         final int curScrollY;
         if (animating) {
@@ -3155,7 +3160,7 @@
                             mFullRedrawNeeded = true;
                             try {
                                 final WindowManager.LayoutParams lp = mWindowAttributes;
-                                final Rect surfaceInsets = lp != null ? lp.shadowInsets : null;
+                                final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
                                 mAttachInfo.mHardwareRenderer.initializeIfNeeded(
                                         mWidth, mHeight, mSurface, surfaceInsets);
                             } catch (OutOfResourcesException e) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index aa71ed8..c169d35 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -23,6 +23,8 @@
 import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -157,7 +159,7 @@
     private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw";
 
     private final Context mContext;
-    
+
     private TypedArray mWindowStyle;
     private Callback mCallback;
     private OnWindowDismissedCallback mOnWindowDismissedCallback;
@@ -181,7 +183,7 @@
     private int mDefaultWindowFormat = PixelFormat.OPAQUE;
 
     private boolean mHasSoftInputMode = false;
-    
+
     private boolean mDestroyed;
 
     // The current window attributes.
@@ -227,7 +229,7 @@
          * @return boolean Return true if this event was consumed.
          */
         public boolean dispatchTouchEvent(MotionEvent event);
-        
+
         /**
          * Called to process trackball events.  At the very least your
          * implementation must call
@@ -313,14 +315,14 @@
          * Called when a panel's menu is opened by the user. This may also be
          * called when the menu is changing from one type to another (for
          * example, from the icon menu to the expanded menu).
-         * 
+         *
          * @param featureId The panel that the menu is in.
          * @param menu The menu that is opened.
          * @return Return true to allow the menu to open, or false to prevent
          *         the menu from opening.
          */
         public boolean onMenuOpened(int featureId, Menu menu);
-        
+
         /**
          * Called when a panel's menu item has been selected by the user.
          *
@@ -364,31 +366,31 @@
          * for more information.
          */
         public void onAttachedToWindow();
-        
+
         /**
          * Called when the window has been attached to the window manager.
          * See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()}
          * for more information.
          */
         public void onDetachedFromWindow();
-        
+
         /**
          * Called when a panel is being closed.  If another logical subsequent
          * panel is being opened (and this panel is being closed to make room for the subsequent
          * panel), this method will NOT be called.
-         * 
+         *
          * @param featureId The panel that is being displayed.
          * @param menu If onCreatePanelView() returned null, this is the Menu
          *            being displayed in the panel.
          */
         public void onPanelClosed(int featureId, Menu menu);
-        
+
         /**
          * Called when the user signals the desire to start a search.
-         * 
+         *
          * @return true if search launched, false if activity refuses (blocks)
-         * 
-         * @see android.app.Activity#onSearchRequested() 
+         *
+         * @see android.app.Activity#onSearchRequested()
          */
         public boolean onSearchRequested();
 
@@ -457,7 +459,7 @@
             return mWindowStyle;
         }
     }
-    
+
     /**
      * Set the container for this window.  If not set, the DecorWindow
      * operates as a top-level window; otherwise, it negotiates with the
@@ -488,7 +490,7 @@
     public final boolean hasChildren() {
         return mHasChildren;
     }
-    
+
     /** @hide */
     public final void destroy() {
         mDestroyed = true;
@@ -622,14 +624,14 @@
      * callback will be used to tell you about state changes to the surface.
      */
     public abstract void takeSurface(SurfaceHolder.Callback2 callback);
-    
+
     /**
      * Take ownership of this window's InputQueue.  The window will no
      * longer read and dispatch input events from the queue; it is your
      * responsibility to do so.
      */
     public abstract void takeInputQueue(InputQueue.Callback callback);
-    
+
     /**
      * Return whether this window is being displayed with a floating style
      * (based on the {@link android.R.attr#windowIsFloating} attribute in
@@ -740,7 +742,7 @@
         }
         dispatchWindowAttributesChanged(attrs);
     }
-    
+
     /**
      * Convenience function to set the flag bits as specified in flags, as
      * per {@link #setFlags}.
@@ -756,7 +758,7 @@
     public void addPrivateFlags(int flags) {
         setPrivateFlags(flags, flags);
     }
-    
+
     /**
      * Convenience function to clear the flag bits as specified in flags, as
      * per {@link #setFlags}.
@@ -772,7 +774,7 @@
      * Set the flags of the window, as per the
      * {@link WindowManager.LayoutParams WindowManager.LayoutParams}
      * flags.
-     * 
+     *
      * <p>Note that some flags must be set before the window decoration is
      * created (by the first call to
      * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} or
@@ -859,20 +861,20 @@
     protected final int getForcedWindowFlags() {
         return mForcedWindowFlags;
     }
-    
+
     /**
      * Has the app specified their own soft input mode?
      */
     protected final boolean hasSoftInputMode() {
         return mHasSoftInputMode;
     }
-    
+
     /** @hide */
     public void setCloseOnTouchOutside(boolean close) {
         mCloseOnTouchOutside = close;
         mSetCloseOnTouchOutside = true;
     }
-    
+
     /** @hide */
     public void setCloseOnTouchOutsideIfNotSet(boolean close) {
         if (!mSetCloseOnTouchOutside) {
@@ -880,10 +882,10 @@
             mSetCloseOnTouchOutside = true;
         }
     }
-    
+
     /** @hide */
     public abstract void alwaysReadCloseOnTouchAttr();
-    
+
     /** @hide */
     public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
         if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
@@ -892,7 +894,7 @@
         }
         return false;
     }
-    
+
     private boolean isOutOfBounds(Context context, MotionEvent event) {
         final int x = (int) event.getX();
         final int y = (int) event.getY();
@@ -902,7 +904,7 @@
                 || (x > (decorView.getWidth()+slop))
                 || (y > (decorView.getHeight()+slop));
     }
-    
+
     /**
      * Enable extended screen features.  This must be called before
      * setContentView().  May be called as many times as desired as long as it
@@ -989,7 +991,7 @@
      * of the window that can not, from this point forward, be changed: the
      * features that have been requested with {@link #requestFeature(int)},
      * and certain window flags as described in {@link #setFlags(int, int)}.
-     * 
+     *
      * @param view The desired content to display.
      * @param params Layout parameters for the view.
      */
@@ -1037,7 +1039,7 @@
     public abstract void togglePanel(int featureId, KeyEvent event);
 
     public abstract void invalidatePanelMenu(int featureId);
-    
+
     public abstract boolean performPanelShortcut(int featureId,
                                                  int keyCode,
                                                  KeyEvent event,
@@ -1052,17 +1054,17 @@
 
     /**
      * Should be called when the configuration is changed.
-     * 
+     *
      * @param newConfig The new configuration.
      */
     public abstract void onConfigurationChanged(Configuration newConfig);
-    
+
     /**
      * Change the background of this window to a Drawable resource. Setting the
      * background to null will make the window be opaque. To make the window
      * transparent, you can use an empty drawable (for instance a ColorDrawable
      * with the color 0 or the system drawable android:drawable/empty.)
-     * 
+     *
      * @param resid The resource identifier of a drawable resource which will be
      *              installed as the new background.
      */
@@ -1173,7 +1175,7 @@
      *
      */
     public abstract boolean superDispatchTouchEvent(MotionEvent event);
-    
+
     /**
      * Used by custom windows, such as Dialog, to pass the trackball event
      * further down the view hierarchy. Application developers should
@@ -1181,7 +1183,7 @@
      *
      */
     public abstract boolean superDispatchTrackballEvent(MotionEvent event);
-    
+
     /**
      * Used by custom windows, such as Dialog, to pass the generic motion event
      * further down the view hierarchy. Application developers should
@@ -1194,11 +1196,11 @@
      * Retrieve the top-level window decor view (containing the standard
      * window frame/decorations and the client's content inside of that), which
      * can be added as a window to the window manager.
-     * 
+     *
      * <p><em>Note that calling this function for the first time "locks in"
      * various window characteristics as described in
      * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
-     * 
+     *
      * @return Returns the top-level window decor view.
      */
     public abstract View getDecorView();
@@ -1206,16 +1208,16 @@
     /**
      * Retrieve the current decor view, but only if it has already been created;
      * otherwise returns null.
-     * 
+     *
      * @return Returns the top-level window decor or null.
      * @see #getDecorView
      */
     public abstract View peekDecorView();
 
     public abstract Bundle saveHierarchyState();
-    
+
     public abstract void restoreHierarchyState(Bundle savedInstanceState);
-    
+
     protected abstract void onActive();
 
     /**
@@ -1233,10 +1235,10 @@
     {
         return mFeatures;
     }
-    
+
     /**
      * Query for the availability of a certain feature.
-     * 
+     *
      * @param feature The feature ID to check
      * @return true if the feature is enabled, false otherwise.
      */
@@ -1290,9 +1292,9 @@
      * @param event the {@link android.view.KeyEvent} to use to help check.
      */
     public abstract boolean isShortcutKey(int keyCode, KeyEvent event);
-    
+
     /**
-     * @see android.app.Activity#setVolumeControlStream(int) 
+     * @see android.app.Activity#setVolumeControlStream(int)
      */
     public abstract void setVolumeControlStream(int streamType);
 
@@ -1302,6 +1304,19 @@
     public abstract int getVolumeControlStream();
 
     /**
+     * @see android.app.Activity#setMediaController(android.media.session.MediaController)
+     */
+    public void setMediaController(MediaController controller) {
+    }
+
+    /**
+     * @see android.app.Activity#getMediaController()
+     */
+    public MediaController getMediaController() {
+        return null;
+    }
+
+    /**
      * Set extra options that will influence the UI for this window.
      * @param uiOptions Flags specifying extra options for this window.
      */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c06b5d8..034778f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1298,7 +1298,7 @@
          *
          * @hide
          */
-        public Rect shadowInsets = new Rect();
+        public Rect surfaceInsets = new Rect();
     
         /**
          * The desired bitmap format.  May be one of the constants in
@@ -1580,10 +1580,10 @@
             out.writeInt(hasSystemUiListeners ? 1 : 0);
             out.writeInt(inputFeatures);
             out.writeLong(userActivityTimeout);
-            out.writeInt(shadowInsets.left);
-            out.writeInt(shadowInsets.top);
-            out.writeInt(shadowInsets.right);
-            out.writeInt(shadowInsets.bottom);
+            out.writeInt(surfaceInsets.left);
+            out.writeInt(surfaceInsets.top);
+            out.writeInt(surfaceInsets.right);
+            out.writeInt(surfaceInsets.bottom);
         }
         
         public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1626,7 +1626,10 @@
             hasSystemUiListeners = in.readInt() != 0;
             inputFeatures = in.readInt();
             userActivityTimeout = in.readLong();
-            shadowInsets.set(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+            surfaceInsets.left = in.readInt();
+            surfaceInsets.top = in.readInt();
+            surfaceInsets.right = in.readInt();
+            surfaceInsets.bottom = in.readInt();
         }
     
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -1658,7 +1661,7 @@
         /** {@hide} */
         public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19;
         /** {@hide} */
-        public static final int SHADOW_INSETS_CHANGED = 1<<20;
+        public static final int SURFACE_INSETS_CHANGED = 1<<20;
         /** {@hide} */
         public static final int EVERYTHING_CHANGED = 0xffffffff;
 
@@ -1794,9 +1797,9 @@
                 changes |= USER_ACTIVITY_TIMEOUT_CHANGED;
             }
 
-            if (!shadowInsets.equals(o.shadowInsets)) {
-                shadowInsets.set(o.shadowInsets);
-                changes |= SHADOW_INSETS_CHANGED;
+            if (!surfaceInsets.equals(o.surfaceInsets)) {
+                surfaceInsets.set(o.surfaceInsets);
+                changes |= SURFACE_INSETS_CHANGED;
             }
 
             return changes;
@@ -1898,8 +1901,8 @@
             if (userActivityTimeout >= 0) {
                 sb.append(" userActivityTimeout=").append(userActivityTimeout);
             }
-            if (!shadowInsets.equals(Insets.NONE)) {
-                sb.append(" shadowInsets=").append(shadowInsets);
+            if (!surfaceInsets.equals(Insets.NONE)) {
+                sb.append(" surfaceInsets=").append(surfaceInsets);
             }
             sb.append('}');
             return sb.toString();
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/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl b/core/java/android/widget/OnDateChangedListener.java
similarity index 65%
rename from telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
rename to core/java/android/widget/OnDateChangedListener.java
index 3a02b060..29be888 100644
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
+++ b/core/java/android/widget/OnDateChangedListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony;
+package android.widget;
 
 /**
- * Callback interface for when DTMF has been sent.
+ * The callback used to notify other date picker components of a change in the selected date.
+ *
  */
-oneway interface IThirdPartyCallSendDtmfCallback {
-    /**
-     * Called when the DTMF code has been sent.
-     */
-    void onSendDtmfCompleted();
+interface OnDateChangedListener {
+
+    public void onDateChanged();
 }
+
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index a35d447..41d3e320 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1096,10 +1096,6 @@
         p.softInputMode = mSoftInputMode;
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
 
-        // TODO: Use real shadow insets once that algorithm is finalized.
-        final int shadowInset = (int) Math.ceil(mElevation * 2);
-        p.shadowInsets.set(shadowInset, shadowInset, shadowInset, shadowInset);
-
         return p;
     }
 
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/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index a866ca7..229df8f 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -36,7 +36,6 @@
 import android.widget.TextView;
 
 import java.text.Collator;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -106,22 +105,21 @@
         return constructAdapter(context, layoutId, fieldId, false /* disable pseudolocales */);
     }
 
-    public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
-            final int layoutId, final int fieldId, final boolean isInDeveloperMode) {
+    public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
         final Resources resources = context.getResources();
 
-        String[] locales = Resources.getSystem().getAssets().getLocales();
+        final String[] locales = Resources.getSystem().getAssets().getLocales();
         List<String> localeList = new ArrayList<String>(locales.length);
         Collections.addAll(localeList, locales);
         if (isInDeveloperMode) {
             if (!localeList.contains("zz_ZZ")) {
                 localeList.add("zz_ZZ");
             }
-        /** - TODO: Enable when zz_ZY Pseudolocale is complete
-         *  if (!localeList.contains("zz_ZY")) {
-         *      localeList.add("zz_ZY");
-         *  }
-         */
+            /** - TODO: Enable when zz_ZY Pseudolocale is complete
+             *  if (!localeList.contains("zz_ZY")) {
+             *      localeList.add("zz_ZY");
+             *	}
+             */
         }
 
         Collections.sort(localeList);
@@ -179,6 +177,13 @@
         }
 
         Collections.sort(localeInfos);
+        return localeInfos;
+    }
+
+    public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
+            final int layoutId, final int fieldId, final boolean isInDeveloperMode) {
+        final List<LocaleInfo> localeInfos = getAllAssetLocales(context, isInDeveloperMode);
+
         final LayoutInflater inflater =
                 (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos) {
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 8b56ceb..7e58351 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -56,7 +56,7 @@
 
 public class LocalTransport extends BackupTransport {
     private static final String TAG = "LocalTransport";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private static final String TRANSPORT_DIR_NAME
             = "com.android.internal.backup.LocalTransport";
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/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index b8fcff6..a890eb4 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -39,14 +39,12 @@
 jfieldID gOptions_mimeFieldID;
 jfieldID gOptions_mCancelID;
 jfieldID gOptions_bitmapFieldID;
-jfieldID gBitmap_nativeBitmapFieldID;
-jfieldID gBitmap_layoutBoundsFieldID;
 
-#if 0
-    #define TRACE_BITMAP(code)  code
-#else
-    #define TRACE_BITMAP(code)
-#endif
+jfieldID gBitmap_nativeBitmapFieldID;
+jfieldID gBitmap_ninePatchInsetsFieldID;
+
+jclass gInsetStruct_class;
+jmethodID gInsetStruct_constructorMethodID;
 
 using namespace android;
 
@@ -195,8 +193,7 @@
     const unsigned int mSize;
 };
 
-static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
-        jobject options) {
+static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
 
     int sampleSize = 1;
 
@@ -331,12 +328,12 @@
     }
 
     jbyteArray ninePatchChunk = NULL;
-    if (peeker.fPatch != NULL) {
+    if (peeker.mPatch != NULL) {
         if (willScale) {
-            scaleNinePatchChunk(peeker.fPatch, scale);
+            scaleNinePatchChunk(peeker.mPatch, scale);
         }
 
-        size_t ninePatchArraySize = peeker.fPatch->serializedSize();
+        size_t ninePatchArraySize = peeker.mPatch->serializedSize();
         ninePatchChunk = env->NewByteArray(ninePatchArraySize);
         if (ninePatchChunk == NULL) {
             return nullObjectReturn("ninePatchChunk == null");
@@ -347,28 +344,18 @@
             return nullObjectReturn("primitive array == null");
         }
 
-        memcpy(array, peeker.fPatch, peeker.fPatchSize);
+        memcpy(array, peeker.mPatch, peeker.mPatchSize);
         env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
     }
 
-    jintArray layoutBounds = NULL;
-    if (peeker.fLayoutBounds != NULL) {
-        layoutBounds = env->NewIntArray(4);
-        if (layoutBounds == NULL) {
-            return nullObjectReturn("layoutBounds == null");
-        }
-
-        jint scaledBounds[4];
-        if (willScale) {
-            for (int i=0; i<4; i++) {
-                scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f);
-            }
-        } else {
-            memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds));
-        }
-        env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds);
+    jobject ninePatchInsets = NULL;
+    if (peeker.mHasInsets) {
+        ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
+                peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
+                peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
+                peeker.mOutlineRadius, peeker.mOutlineFilled, scale);
         if (javaBitmap != NULL) {
-            env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds);
+            env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
         }
     }
 
@@ -409,10 +396,10 @@
     }
 
     if (padding) {
-        if (peeker.fPatch != NULL) {
+        if (peeker.mPatch != NULL) {
             GraphicsJNI::set_jrect(env, padding,
-                    peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
-                    peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
+                    peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
+                    peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
         } else {
             GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
         }
@@ -446,7 +433,7 @@
 
     // now create the java bitmap
     return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(),
-            bitmapCreateFlags, ninePatchChunk, layoutBounds, -1);
+            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
 }
 
 // Need to buffer enough input to be able to rewind as much as might be read by a decoder
@@ -576,7 +563,7 @@
     jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options");
     SkASSERT(options_class);
     gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap",
-        "Landroid/graphics/Bitmap;");
+            "Landroid/graphics/Bitmap;");
     gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z");
     gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I");
     gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig",
@@ -598,7 +585,12 @@
     jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
     SkASSERT(bitmap_class);
     gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "J");
-    gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mOpticalInsets", "[I");
+    gBitmap_ninePatchInsetsFieldID = getFieldIDCheck(env, bitmap_class, "mNinePatchInsets",
+            "Landroid/graphics/NinePatch$InsetStruct;");
+
+    gInsetStruct_class = (jclass) env->NewGlobalRef(env->FindClass("android/graphics/NinePatch$InsetStruct"));
+    gInsetStruct_constructorMethodID = env->GetMethodID(gInsetStruct_class, "<init>", "(IIIIIIIIFZF)V");
+
     int ret = AndroidRuntime::registerNativeMethods(env,
                                     "android/graphics/BitmapFactory$Options",
                                     gOptionsMethods,
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 6c7962e..320c259 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;
 }
@@ -414,7 +415,7 @@
 }
 
 jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
-        int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density)
+        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density)
 {
     SkASSERT(bitmap);
     SkASSERT(bitmap->pixelRef());
@@ -428,17 +429,11 @@
     jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
             reinterpret_cast<jlong>(bitmap), buffer,
             bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
-            ninepatch, layoutbounds);
+            ninePatchChunk, ninePatchInsets);
     hasException(env); // For the side effect of logging.
     return obj;
 }
 
-jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
-        jbyteArray ninepatch, int density)
-{
-    return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninepatch, NULL, density);
-}
-
 void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap,
         bool isPremultiplied)
 {
@@ -719,7 +714,7 @@
 
     gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
     gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "J");
-    gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[B[I)V");
+    gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");
     gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V");
     gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
     gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index a03391d..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);
@@ -76,10 +75,12 @@
         bitmap's SkAlphaType must already be in sync with bitmapCreateFlags.
     */
     static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
-            int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density = -1);
+            int bitmapCreateFlags, jbyteArray ninePatch, jobject ninePatchInsets, int density = -1);
 
     static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
-            jbyteArray ninepatch, int density = -1);
+            jbyteArray ninePatch, int density = -1) {
+        return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninePatch, NULL, density);
+    }
 
     /** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
         sync with isPremultiplied
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/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 5daa1ad..ea5193b 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -21,7 +21,7 @@
 using namespace android;
 
 bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
-    if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
+    if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) {
         Res_png_9patch* patch = (Res_png_9patch*) data;
         size_t patchSize = patch->serializedSize();
         assert(length == patchSize);
@@ -30,12 +30,9 @@
         memcpy(patchNew, patch, patchSize);
         Res_png_9patch::deserialize(patchNew);
         patchNew->fileToDevice();
-        free(fPatch);
-        fPatch = patchNew;
-        fPatchSize = patchSize;
-        //printf("9patch: (%d,%d)-(%d,%d)\n",
-        //       fPatch.sizeLeft, fPatch.sizeTop,
-        //       fPatch.sizeRight, fPatch.sizeBottom);
+        free(mPatch);
+        mPatch = patchNew;
+        mPatchSize = patchSize;
 
         // now update our host to force index or 32bit config
         // 'cause we don't want 565 predithered, since as a 9patch, we know
@@ -47,10 +44,15 @@
         table.fPrefFor_8bpc_NoAlpha_src     = SkBitmap::kARGB_8888_Config;
         table.fPrefFor_8bpc_YesAlpha_src    = SkBitmap::kARGB_8888_Config;
 
-        fHost->setPrefConfigTable(table);
-    } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) {
-        fLayoutBounds = new int[4];
-        memcpy(fLayoutBounds, data, sizeof(int) * 4);
+        mHost->setPrefConfigTable(table);
+    } else if (!strcmp("npLb", tag) && length == sizeof(int32_t) * 4) {
+        mHasInsets = true;
+        memcpy(&mOpticalInsets, data, sizeof(int32_t) * 4);
+    } else if (!strcmp("npOl", tag) && length == 24) { // 4 int32_ts, 1 float, 1 int32_t sized bool
+        mHasInsets = true;
+        memcpy(&mOutlineInsets, data, sizeof(int32_t) * 4);
+        mOutlineRadius = ((const float*)data)[4];
+        mOutlineFilled = ((const int32_t*)data)[5] & 0x01;
     }
     return true;    // keep on decoding
 }
diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h
index 2043862..8d3e6cf 100644
--- a/core/jni/android/graphics/NinePatchPeeker.h
+++ b/core/jni/android/graphics/NinePatchPeeker.h
@@ -23,26 +23,34 @@
 using namespace android;
 
 class NinePatchPeeker : public SkImageDecoder::Peeker {
-    SkImageDecoder* fHost;
+private:
+    // the host lives longer than we do, so a raw ptr is safe
+    SkImageDecoder* mHost;
 public:
-    NinePatchPeeker(SkImageDecoder* host) {
-        // the host lives longer than we do, so a raw ptr is safe
-        fHost = host;
-        fPatch = NULL;
-        fPatchSize = 0;
-        fLayoutBounds = NULL;
+    NinePatchPeeker(SkImageDecoder* host)
+            : mHost(host)
+            , mPatch(NULL)
+            , mPatchSize(0)
+            , mHasInsets(false)
+            , mOutlineRadius(0)
+            , mOutlineFilled(false) {
+        memset(mOpticalInsets, 0, 4 * sizeof(int32_t));
+        memset(mOutlineInsets, 0, 4 * sizeof(int32_t));
     }
 
     ~NinePatchPeeker() {
-        free(fPatch);
-        delete fLayoutBounds;
+        free(mPatch);
     }
 
-    Res_png_9patch*  fPatch;
-    size_t fPatchSize;
-    int    *fLayoutBounds;
-
     virtual bool peek(const char tag[], const void* data, size_t length);
+
+    Res_png_9patch* mPatch;
+    size_t mPatchSize;
+    bool mHasInsets;
+    int32_t mOpticalInsets[4];
+    int32_t mOutlineInsets[4];
+    float mOutlineRadius;
+    bool mOutlineFilled;
 };
 
 #endif // NinePatchPeeker_h
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/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 9621bb2..d2f5b5d 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -19,17 +19,20 @@
 #include <utils/Log.h>
 #include <utils/Errors.h>
 #include <utils/Trace.h>
+#include <camera/CameraUtils.h>
 
 #include "jni.h"
 #include "JNIHelp.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
+#include "android_runtime/android_graphics_SurfaceTexture.h"
 
 #include <gui/Surface.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <ui/GraphicBuffer.h>
 #include <system/window.h>
 #include <hardware/camera3.h>
+#include <system/camera_metadata.h>
 
 #include <stdint.h>
 #include <inttypes.h>
@@ -335,6 +338,25 @@
     return anw;
 }
 
+static sp<ANativeWindow> getNativeWindowFromTexture(JNIEnv* env, jobject surfaceTexture) {
+    sp<ANativeWindow> anw;
+    if (surfaceTexture) {
+        anw = android_SurfaceTexture_getNativeWindow(env, surfaceTexture);
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+    } else {
+        jniThrowNullPointerException(env, "surfaceTexture");
+        return NULL;
+    }
+    if (anw == NULL) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "SurfaceTexture had no valid native window.");
+        return NULL;
+    }
+    return anw;
+}
+
 static sp<Surface> getSurface(JNIEnv* env, jobject surface) {
     sp<Surface> s;
     if (surface) {
@@ -376,6 +398,17 @@
 static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
           jobject surface, jintArray dimens) {
     ALOGV("nativeGetSurfaceDimens");
+
+    if (dimens == NULL) {
+        ALOGE("%s: Null dimens argument passed to nativeDetectSurfaceDimens", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    if (env->GetArrayLength(dimens) < 2) {
+        ALOGE("%s: Invalid length of dimens argument in nativeDetectSurfaceDimens", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
     sp<ANativeWindow> anw;
     if ((anw = getNativeWindow(env, surface)) == NULL) {
         ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
@@ -398,6 +431,37 @@
     return NO_ERROR;
 }
 
+static jint LegacyCameraDevice_nativeDetectTextureDimens(JNIEnv* env, jobject thiz,
+        jobject surfaceTexture, jintArray dimens) {
+    ALOGV("nativeDetectTextureDimens");
+    sp<ANativeWindow> anw;
+    if ((anw = getNativeWindowFromTexture(env, surfaceTexture)) == NULL) {
+        ALOGE("%s: Could not retrieve native window from SurfaceTexture.", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    int32_t dimenBuf[2];
+    status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
+    if(err != NO_ERROR) {
+        ALOGE("%s: Error while querying SurfaceTexture width %s (%d)", __FUNCTION__,
+                strerror(-err), err);
+        return err;
+    }
+
+    err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
+    if(err != NO_ERROR) {
+        ALOGE("%s: Error while querying SurfaceTexture height %s (%d)", __FUNCTION__,
+                strerror(-err), err);
+        return err;
+    }
+
+    env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
+    if (env->ExceptionCheck()) {
+        return BAD_VALUE;
+    }
+    return NO_ERROR;
+}
+
 static jint LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface,
         jint width, jint height, jint pixelFormat) {
     ALOGV("nativeConfigureSurface");
@@ -504,6 +568,42 @@
     return reinterpret_cast<jlong>(b.get());
 }
 
+static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject thiz,
+        jobject surface, jint facing, jint orientation) {
+    ALOGV("nativeSetSurfaceOrientation");
+    sp<ANativeWindow> anw;
+    if ((anw = getNativeWindow(env, surface)) == NULL) {
+        ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    status_t err = NO_ERROR;
+    CameraMetadata staticMetadata;
+
+    int32_t orientVal = static_cast<int32_t>(orientation);
+    uint8_t facingVal = static_cast<uint8_t>(facing);
+    staticMetadata.update(ANDROID_SENSOR_ORIENTATION, &orientVal, 1);
+    staticMetadata.update(ANDROID_LENS_FACING, &facingVal, 1);
+
+    int32_t transform = 0;
+
+    if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != OK) {
+        ALOGE("%s: Invalid rotation transform %s (%d)", __FUNCTION__, strerror(-err),
+                err);
+        return err;
+    }
+
+    ALOGV("%s: Setting buffer sticky transform to %d", __FUNCTION__, transform);
+
+    if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != OK) {
+        ALOGE("%s: Unable to configure surface transform, error %s (%d)", __FUNCTION__,
+                strerror(-err), err);
+        return err;
+    }
+
+    return NO_ERROR;
+}
+
 } // extern "C"
 
 static JNINativeMethod gCameraDeviceMethods[] = {
@@ -528,6 +628,12 @@
     { "nativeGetSurfaceId",
     "(Landroid/view/Surface;)J",
     (void *)LegacyCameraDevice_nativeGetSurfaceId },
+    { "nativeDetectTextureDimens",
+    "(Landroid/graphics/SurfaceTexture;[I)I",
+    (void *)LegacyCameraDevice_nativeDetectTextureDimens },
+    { "nativeSetSurfaceOrientation",
+    "(Landroid/view/Surface;II)I",
+    (void *)LegacyCameraDevice_nativeSetSurfaceOrientation },
 };
 
 // Get all the required offsets in java class and register native functions
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 989b60ee..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>
@@ -439,8 +440,11 @@
     if (!is_system_server) {
         int rc = createProcessGroup(uid, getpid());
         if (rc != 0) {
-            ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc));
-            RuntimeAbort(env);
+            if (rc == -EROFS) {
+                ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
+            } else {
+                ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc));
+            }
         }
     }
 
@@ -533,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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fc12a6e..a7dba66 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2163,7 +2163,7 @@
         android:description="@string/permdesc_bindTvInput"
         android:protectionLevel="signature|system" />
 
-    <!-- Must be required by a {@link android.media.routeprovider.RouteProviderService}
+    <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
          @hide -->
     <permission android:name="android.permission.BIND_ROUTE_PROVIDER"
@@ -2734,6 +2734,13 @@
         android:description="@string/permdesc_bindConditionProviderService"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a {@link android.media.routing.MediaRouteService},
+         to ensure that only the system can bind to it. -->
+    <permission android:name="android.permission.BIND_MEDIA_ROUTE_SERVICE"
+        android:label="@string/permlab_bindMediaRouteService"
+        android:description="@string/permdesc_bindMediaRouteService"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by an {@link android.service.dreams.DreamService},
          to ensure that only the system can bind to it. -->
     <permission android:name="android.permission.BIND_DREAM_SERVICE"
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/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/alert_dialog_material.xml b/core/res/res/layout/alert_dialog_material.xml
index 93acc3f..6a435b2 100644
--- a/core/res/res/layout/alert_dialog_material.xml
+++ b/core/res/res/layout/alert_dialog_material.xml
@@ -20,13 +20,7 @@
     android:id="@+id/parentPanel"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:background="@drawable/dialog_background_material"
-    android:translationZ="@dimen/floating_window_z"
-    android:layout_marginLeft="@dimen/floating_window_margin_left"
-    android:layout_marginTop="@dimen/floating_window_margin_top"
-    android:layout_marginRight="@dimen/floating_window_margin_right"
-    android:layout_marginBottom="@dimen/floating_window_margin_bottom">
+    android:orientation="vertical">
 
     <LinearLayout android:id="@+id/topPanel"
         android:layout_width="match_parent"
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 5fdadb7..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" />
 
@@ -1826,6 +1853,9 @@
              Activity Transition. Corresponds to
              {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. -->
         <attr name="windowTransitionBackgroundFadeDuration" />
+
+        <!-- Elevation to use for the window. -->
+        <attr name="windowElevation" format="dimension" />
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -1889,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" />
@@ -4172,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">
@@ -7158,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 53d9cee..0954ddf9 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>
 
@@ -1578,6 +1585,9 @@
     <!-- Package name for default network scorer app; overridden by product overlays. -->
     <string name="config_defaultNetworkScorerPackageName"></string>
 
+    <!-- default device has recents property -->
+    <bool name="config_hasRecents">true</bool>
+
     <!-- Defines the default set of global actions. Actions may still be disabled or hidden based
          on the current state of the device.
          Each item must be one of the following strings:
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 a0ca06e..7b9a38f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2233,6 +2233,23 @@
   <public type="attr" name="buttonBarNegativeButtonStyle" />
   <public type="attr" name="popupElevation" />
   <public type="attr" name="actionBarPopupTheme" />
+  <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" />
 
@@ -2480,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. -->
@@ -2509,6 +2528,4 @@
   <!-- A transition that moves views in or out of the scene to or from the left edge when
        a view visibility changes. -->
   <public type="transition" name="slide_left"/>
-  <public type="attr" name="multiArch" />
-  <public type="attr" name="touchscreenBlocksFocus" />
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index bab9d76..0285f54 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2124,6 +2124,11 @@
     <string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider service. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindMediaRouteService">bind to a media route service</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindMediaRouteService">Allows the holder to bind to the top-level interface of a media route service. Should never be needed for normal apps.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_bindDreamService">bind to a dream service</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_bindDreamService">Allows the holder to bind to the top-level interface of a dream service. Should never be needed for normal apps.</string>
@@ -4787,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 0eceae6..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>
 
@@ -824,15 +826,9 @@
         <item name="textColorLink">?textColorLinkInverse</item>
     </style>
 
-    <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme">
-    </style>
+    <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme" />
 
-    <style name="TextAppearance.Theme.Dialog.AppError">
-        <item name="textColor">#ffffc0c0</item>
-    </style>
-
-    <style name="TextAppearance.Widget">
-    </style>
+    <style name="TextAppearance.Widget" />
 
     <style name="TextAppearance.Widget.Button" parent="TextAppearance.Small.Inverse">
         <item name="textColor">@color/primary_text_light_nodisable</item>
@@ -1206,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">
@@ -1342,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>
@@ -1366,8 +1356,26 @@
         <item name="lightY">-200dp</item>
         <item name="lightZ">800dp</item>
         <item name="lightRadius">800dp</item>
-        <item name="ambientShadowAlpha">0.125</item>
-        <item name="spotShadowAlpha">0.25</item>
+        <item name="ambientShadowAlpha">0.0471</item>
+        <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 b22155c..46e0194 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -289,6 +289,7 @@
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
   <java-symbol type="bool" name="config_windowIsRound" />
+  <java-symbol type="bool" name="config_hasRecents" />
 
   <java-symbol type="integer" name="config_bluetooth_max_advertisers" />
   <java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
@@ -610,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" />
@@ -1181,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" />
@@ -1839,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" />
@@ -1852,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" />
@@ -1863,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" />
@@ -1884,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 6ada975..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
@@ -820,21 +855,14 @@
     <eat-comment />
 
     <!-- Theme for the dialog shown when an app crashes or ANRs. -->
-    <style name="Theme.Dialog.AppError" parent="Theme.DeviceDefault.Light.Dialog">
-        <item name="windowFrame">@null</item>
-        <item name="windowTitleStyle">@style/DialogWindowTitle</item>
-        <item name="windowBackground">@color/transparent</item>
-        <item name="windowIsFloating">true</item>
-        <item name="windowContentOverlay">@null</item>
+    <style name="Theme.Dialog.AppError" parent="Theme.DeviceDefault.Light.Dialog.Alert">
         <item name="windowContentTransitions">false</item>
-        <item name="textAppearance">@style/TextAppearance.Theme.Dialog.AppError</item>
         <item name="windowCloseOnTouchOutside">false</item>
     </style>
 
     <!-- Special theme for the recent apps dialog, to allow customization
          with overlays. -->
     <style name="Theme.Dialog.RecentApplications" parent="Theme.DeviceDefault.Light.Dialog">
-        <item name="windowFrame">@null</item>
         <item name="windowBackground">@color/transparent</item>
         <item name="windowAnimationStyle">@style/Animation.RecentApplications</item>
         <item name="textColor">@color/secondary_text_nofocus</item>
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 47f3778..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>
@@ -1011,7 +1065,8 @@
     <style name="Theme.Material.Dialog">
         <item name="windowFrame">@null</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
-        <item name="windowBackground">@drawable/dialog_background_shadow_material</item>
+        <item name="windowBackground">@drawable/dialog_background_material</item>
+        <item name="windowElevation">@dimen/floating_window_z</item>
         <item name="windowIsFloating">true</item>
         <item name="windowContentOverlay">@null</item>
         <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
@@ -1077,6 +1132,7 @@
          its pixels. -->
     <style name="Theme.Material.Dialog.NoFrame">
         <item name="windowBackground">@color/transparent</item>
+        <item name="windowElevation">0dp</item>
         <item name="windowAnimationStyle">@null</item>
         <item name="backgroundDimEnabled">false</item>
         <item name="windowIsTranslucent">true</item>
@@ -1085,7 +1141,6 @@
     </style>
 
     <style name="Theme.Material.Dialog.BaseAlert">
-        <item name="windowBackground">@color/transparent</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
         <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
         <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
@@ -1100,6 +1155,7 @@
 
     <style name="Theme.Material.Dialog.BaseTimePicker">
         <item name="windowBackground">@color/transparent</item>
+        <item name="windowElevation">0dp</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
         <item name="windowContentOverlay">@null</item>
     </style>
@@ -1108,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). -->
@@ -1131,7 +1197,8 @@
     <style name="Theme.Material.Light.Dialog">
         <item name="windowFrame">@null</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
-        <item name="windowBackground">@drawable/dialog_background_shadow_material</item>
+        <item name="windowBackground">@drawable/dialog_background_material</item>
+        <item name="windowElevation">@dimen/floating_window_z</item>
         <item name="windowIsFloating">true</item>
         <item name="windowContentOverlay">@null</item>
         <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
@@ -1203,7 +1270,6 @@
     <style name="Theme.Material.Light.DialogWhenLarge.NoActionBar" parent="@style/Theme.Material.Light.NoActionBar" />
 
     <style name="Theme.Material.Light.Dialog.BaseAlert">
-        <item name="windowBackground">@color/transparent</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
         <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
         <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
@@ -1218,13 +1284,25 @@
 
     <style name="Theme.Material.Light.Dialog.BaseTimePicker">
         <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/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a2cc40c..b524177 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1257,4 +1257,11 @@
     <instrumentation android:name="android.test.InstrumentationTestRunner"
             android:targetPackage="com.android.frameworks.coretests"
             android:label="Frameworks Core Tests" />
+    <key-sets>
+        <key-set android:name="A" >
+          <public-key android:name="keyA"
+                      android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="/>
+        </key-set>
+        <upgrade-key-set android:name="A"/>
+    </key-sets>
 </manifest>
diff --git a/core/tests/coretests/apks/keyset/Android.mk b/core/tests/coretests/apks/keyset/Android.mk
index e44ac6c..306dc90 100644
--- a/core/tests/coretests/apks/keyset/Android.mk
+++ b/core/tests/coretests/apks/keyset/Android.mk
@@ -88,4 +88,21 @@
 LOCAL_CERTIFICATE := $(LOCAL_PATH)/../../certs/keyset_A
 LOCAL_ADDITIONAL_CERTIFICATES := $(LOCAL_PATH)/../../certs/keyset_B
 LOCAL_MANIFEST_FILE := uB/AndroidManifest.xml
+include $(FrameworkCoreTests_BUILD_PACKAGE)
+
+#apks signed by platform only
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_PACKAGE_NAME := keyset_splat_api
+LOCAL_CERTIFICATE := platform
+LOCAL_MANIFEST_FILE := api_test/AndroidManifest.xml
+include $(FrameworkCoreTests_BUILD_PACKAGE)
+
+#apks signed by platform and keyset_A
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_PACKAGE_NAME := keyset_splata_api
+LOCAL_CERTIFICATE := platform
+LOCAL_ADDITIONAL_CERTIFICATES := $(LOCAL_PATH)/../../certs/keyset_A
+LOCAL_MANIFEST_FILE := api_test/AndroidManifest.xml
 include $(FrameworkCoreTests_BUILD_PACKAGE)
\ No newline at end of file
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
similarity index 69%
copy from core/res/res/drawable/lock_task_notify_bg.xml
copy to core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
index 3a8fab5..4c7e968 100644
--- a/core/res/res/drawable/lock_task_notify_bg.xml
+++ b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2010 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,8 @@
      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>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.keysets_api">
+    <application android:hasCode="false">
+    </application>
+</manifest>
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 0244425..3a80309 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.KeySet;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -33,6 +34,7 @@
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -3328,6 +3330,174 @@
     }
 
     /**
+     * The following tests are related to testing KeySets-based API
+     */
+
+    /*
+     * testGetSigningKeySetNull - ensure getSigningKeySet() returns null on null
+     * input and when calling a package other than that which made the call.
+     */
+    public void testGetSigningKeySet() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet ks;
+        try {
+            ks = pm.getSigningKeySet(null);
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getSigningKeySet("keysets.test.bogus.package");
+            assertTrue(false); // should have thrown
+        } catch (IllegalArgumentException e) {
+        }
+        installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            ks = pm.getSigningKeySet(otherPkgName);
+            assertTrue(false); // should have thrown
+        } catch (SecurityException e) {
+        }
+        cleanUpInstall(otherPkgName);
+        ks = pm.getSigningKeySet(mContext.getPackageName());
+        assertNotNull(ks);
+    }
+
+    /*
+     * testGetKeySetByAlias - same as getSigningKeySet, but for keysets defined
+     * by this package.
+     */
+    public void testGetKeySetByAlias() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet ks;
+        try {
+            ks = pm.getKeySetByAlias(null, null);
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias(null, "keysetBogus");
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias("keysets.test.bogus.package", null);
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias("keysets.test.bogus.package", "A");
+            assertTrue(false); // should have thrown
+        } catch(IllegalArgumentException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias(mPkgName, "keysetBogus");
+            assertTrue(false); // should have thrown
+        } catch(IllegalArgumentException e) {
+        }
+        installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            ks = pm.getKeySetByAlias(otherPkgName, "A");
+            assertTrue(false); // should have thrown
+        } catch (SecurityException e) {
+        }
+        cleanUpInstall(otherPkgName);
+        ks = pm.getKeySetByAlias(mPkgName, "A");
+        assertNotNull(ks);
+    }
+
+    public void testIsSignedBy() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
+        KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
+
+        try {
+            assertFalse(pm.isSignedBy(null, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(null, mSigningKS));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(mPkgName, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy("keysets.test.bogus.package", mDefinedKS));
+        } catch(IllegalArgumentException e) {
+        }
+        assertFalse(pm.isSignedBy(mPkgName, mDefinedKS));
+        assertFalse(pm.isSignedBy(mPkgName, new KeySet(new Binder())));
+        assertTrue(pm.isSignedBy(mPkgName, mSigningKS));
+
+        installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
+        assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+        cleanUpInstall(otherPkgName);
+
+        installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
+        assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+        cleanUpInstall(otherPkgName);
+    }
+
+    public void testIsSignedByExactly() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
+        KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
+        try {
+            assertFalse(pm.isSignedBy(null, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(null, mSigningKS));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(mPkgName, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedByExactly("keysets.test.bogus.package", mDefinedKS));
+        } catch(IllegalArgumentException e) {
+        }
+        assertFalse(pm.isSignedByExactly(mPkgName, mDefinedKS));
+        assertFalse(pm.isSignedByExactly(mPkgName, new KeySet(new Binder())));
+        assertTrue(pm.isSignedByExactly(mPkgName, mSigningKS));
+
+        installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+        assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
+        cleanUpInstall(otherPkgName);
+
+        installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+        assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
+        cleanUpInstall(otherPkgName);
+    }
+
+
+
+    /**
      * The following tests are related to testing the checkSignatures api.
      */
     private void checkSignatures(int apk1, int apk2, int expMatchResult) throws Exception {
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/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/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/media/camera.jd b/docs/html/guide/topics/media/camera.jd
index 56ef624..8b79b23 100644
--- a/docs/html/guide/topics/media/camera.jd
+++ b/docs/html/guide/topics/media/camera.jd
@@ -86,14 +86,17 @@
 
 <h2 id="basics">The Basics</h2>
 <p>The Android framework supports capturing images and video through the
-{@link android.hardware.Camera} API or camera {@link android.content.Intent}. Here are the relevant
+{@link android.hardware.camera2} API or camera {@link android.content.Intent}. Here are the relevant
 classes:</p>
 
 <dl>
-  <dt>{@link android.hardware.Camera}</dt>
-  <dd>This class is the primary API for controlling device cameras. This class is used to take
+  <dt>{@link android.hardware.camera2}</dt>
+  <dd>This package is the primary API for controlling device cameras. It can be used to take
 pictures or videos when you are building a camera application.</dd>
 
+  <dt>{@link android.hardware.Camera}</dt>
+  <dd>This class is the older deprecated API for controlling device cameras.</dd>
+
   <dt>{@link android.view.SurfaceView}</dt>
   <dd>This class is used to present a live camera preview to the user.</dd>
 
@@ -354,6 +357,10 @@
 code than <a href="#intents">using an intent</a>, but it can provide a more compelling experience
 for your users.</p>
 
+<p><strong> Note: The following guide is for the older, deprecated {@link android.hardware.Camera}
+API. For new or advanced camera applications, the newer {@link android.hardware.camera2} API is
+recommended.</strong></p>
+
 <p>The general steps for creating a custom camera interface for your application are as follows:</p>
 
 <ul>
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..b77a72a 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>
@@ -191,7 +349,8 @@
 <input id="agree" type="checkbox" name="agree" value="1" onclick="onAgreeChecked()" />
 <label id="agreeLabel" for="agree">I have read and agree with the above terms and conditions</label>
 </p>
-<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return onDownloadNdkForRealz(this);"></a></p>
+<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return
+onDownloadNdkForRealz(this);"></a></p>
 </div>
 
 
@@ -272,6 +431,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 +619,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 +657,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 +672,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 +972,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 +1057,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 +1273,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 +1418,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 +1428,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 +1523,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 +1721,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 +1762,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 +1950,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 +2113,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 +2852,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 +2896,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 +2966,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 +2993,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 +3032,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 +3064,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 +3415,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 +3434,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 +3478,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 +3553,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 +3606,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 +3688,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..a37afe07 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/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 9561ac4..4b00e22 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -73,8 +73,8 @@
      */
     private boolean mRequestPremultiplied;
 
-    private byte[] mNinePatchChunk;   // may be null
-    private int[] mOpticalInsets;     // may be null
+    private byte[] mNinePatchChunk; // may be null
+    private NinePatch.InsetStruct mNinePatchInsets; // may be null
     private int mWidth;
     private int mHeight;
     private boolean mRecycled;
@@ -111,7 +111,7 @@
     @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
     Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
             boolean isMutable, boolean requestPremultiplied,
-            byte[] ninePatchChunk, int[] opticalInsets) {
+            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
         if (nativeBitmap == 0) {
             throw new RuntimeException("internal error: native bitmap is 0");
         }
@@ -126,7 +126,7 @@
         mFinalizer = new BitmapFinalizer(nativeBitmap);
 
         mNinePatchChunk = ninePatchChunk;
-        mOpticalInsets = opticalInsets;
+        mNinePatchInsets = ninePatchInsets;
         if (density >= 0) {
             mDensity = density;
         }
@@ -946,16 +946,18 @@
      * @hide
      */
     public void getOpticalInsets(@NonNull Rect outInsets) {
-        if (mOpticalInsets == null) {
+        if (mNinePatchInsets == null) {
             outInsets.setEmpty();
         } else {
-            outInsets.left = mOpticalInsets[0];
-            outInsets.top = mOpticalInsets[1];
-            outInsets.right = mOpticalInsets[2];
-            outInsets.bottom = mOpticalInsets[3];
+            outInsets.set(mNinePatchInsets.opticalRect);
         }
     }
 
+    /** @hide */
+    public NinePatch.InsetStruct getNinePatchInsets() {
+        return mNinePatchInsets;
+    }
+
     /**
      * Specifies the known formats a bitmap can be compressed into
      */
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/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 28fd7ba..ab4258d 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -54,9 +54,12 @@
      * cr_offset = y_size
      * cb_offset = y_size + c_size</pre>
      *
-     * <p>This format is guaranteed to be supported for camera preview images since
-     * API level 12; for earlier API versions, check
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>For the older camera API, this format is guaranteed to be supported for
+     * {@link android.hardware.Camera} preview images since API level 12; for earlier API versions,
+     * check {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
      *
      * <p>Note that for camera preview callback use (see
      * {@link android.hardware.Camera#setPreviewCallback}), the
@@ -133,29 +136,47 @@
     public static final int Y16 = 0x20363159;
 
     /**
-     * YCbCr format, used for video. Whether this format is supported by the
-     * camera hardware can be determined by
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     * YCbCr format, used for video.
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>Whether this format is supported by the old camera API can be determined by
+     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
+     *
      */
     public static final int NV16 = 0x10;
 
     /**
-     * YCrCb format used for images, which uses the NV21 encoding format. This
-     * is the default format for camera preview images, when not otherwise set
-     * with {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.
+     * YCrCb format used for images, which uses the NV21 encoding format.
+     *
+     * <p>This is the default format
+     * for {@link android.hardware.Camera} preview images, when not otherwise set with
+     * {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.</p>
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
      */
     public static final int NV21 = 0x11;
 
     /**
      * YCbCr format used for images, which uses YUYV (YUY2) encoding format.
-     * This is an alternative format for camera preview images. Whether this
-     * format is supported by the camera hardware can be determined by
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>This is an alternative format for {@link android.hardware.Camera} preview images. Whether
+     * this format is supported by the camera hardware can be determined by
+     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
      */
     public static final int YUY2 = 0x14;
 
     /**
-     * Encoded formats. These are not necessarily supported by the hardware.
+     * Compressed JPEG format.
+     *
+     * <p>This format is always supported as an output format for the
+     * {@link android.hardware.camera2} API, and as a picture format for the older
+     * {@link android.hardware.Camera} API</p>
      */
     public static final int JPEG = 0x100;
 
@@ -332,16 +353,6 @@
     public static final int RAW10 = 0x25;
 
     /**
-     * Raw bayer format used for images, which is 10 bit precision samples
-     * stored in 16 bit words. The filter pattern is RGGB. Whether this format
-     * is supported by the camera hardware can be determined by
-     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
-     *
-     * @hide
-     */
-    public static final int BAYER_RGGB = 0x200;
-
-    /**
      * Use this function to retrieve the number of bits per pixel of an
      * ImageFormat.
      *
@@ -369,8 +380,6 @@
                 return 12;
             case RAW_SENSOR:
                 return 16;
-            case BAYER_RGGB:
-                return 16;
             case RAW10:
                 return 10;
         }
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index befac92..335bce0 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -16,7 +16,6 @@
 
 package android.graphics;
 
-
 /**
  * The NinePatch class permits drawing a bitmap in nine or more sections.
  * Essentially, it allows the creation of custom graphics that will scale the
@@ -32,6 +31,39 @@
  * </p>
  */
 public class NinePatch {
+    /**
+     * Struct of inset information attached to a 9 patch bitmap.
+     *
+     * Present on a 9 patch bitmap if it optical insets were manually included,
+     * or if outline insets were automatically included by aapt.
+     *
+     * @hide
+     */
+    public static class InsetStruct {
+        @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
+        InsetStruct(int opticalLeft, int opticalTop, int opticalRight, int opticalBottom,
+                int outlineLeft, int outlineTop, int outlineRight, int outlineBottom,
+                float outlineRadius, boolean outlineFilled, float decodeScale) {
+            opticalRect = new Rect(opticalLeft, opticalTop, opticalRight, opticalBottom);
+            outlineRect = new Rect(outlineLeft, outlineTop, outlineRight, outlineBottom);
+
+            if (decodeScale != 1.0f) {
+                // if bitmap was scaled when decoded, scale the insets from the metadata values
+                opticalRect.scale(decodeScale);
+
+                // round inward while scaling outline, as the outline should always be conservative
+                outlineRect.scaleRoundIn(decodeScale);
+            }
+            this.outlineRadius = outlineRadius * decodeScale;
+            this.outlineFilled = outlineFilled;
+        }
+
+        public final Rect opticalRect;
+        public final Rect outlineRect;
+        public final float outlineRadius;
+        public final boolean outlineFilled;
+    }
+
     private final Bitmap mBitmap;
 
     /**
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 437d2f4..a9a8f37 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -609,4 +609,17 @@
             bottom = (int) (bottom * scale + 0.5f);
         }
     }
+
+    /**
+     * Scales up the rect by the given scale, rounding values toward the inside.
+     * @hide
+     */
+    public void scaleRoundIn(float scale) {
+        if (scale != 1.0f) {
+            left = (int) Math.ceil(left * scale);
+            top = (int) Math.ceil(top * scale);
+            right = (int) Math.floor(right * scale);
+            bottom = (int) Math.floor(bottom * scale);
+        }
+    }
 }
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 17795f3..f52c661 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -27,14 +27,18 @@
 /**
  * Captures frames from an image stream as an OpenGL ES texture.
  *
- * <p>The image stream may come from either camera preview or video decode.  A SurfaceTexture
- * may be used in place of a SurfaceHolder when specifying the output destination of a
- * {@link android.hardware.Camera} or {@link android.media.MediaPlayer}
- * object.  Doing so will cause all the frames from the image stream to be sent to the
- * SurfaceTexture object rather than to the device's display.  When {@link #updateTexImage} is
- * called, the contents of the texture object specified when the SurfaceTexture was created are
- * updated to contain the most recent image from the image stream.  This may cause some frames of
- * the stream to be skipped.
+ * <p>The image stream may come from either camera preview or video decode. A
+ * {@link android.view.Surface} created from a SurfaceTexture can be used as an output
+ * destination for the {@link android.hardware.camera2}, {@link android.media.MediaCodec},
+ * {@link android.media.MediaPlayer}, and {@link android.renderscript.Allocation} APIs.
+ * When {@link #updateTexImage} is called, the contents of the texture object specified
+ * when the SurfaceTexture was created are updated to contain the most recent image from the image
+ * stream.  This may cause some frames of the stream to be skipped.
+ *
+ * <p>A SurfaceTexture may also be used in place of a SurfaceHolder when specifying the output
+ * destination of the older {@link android.hardware.Camera} API. Doing so will cause all the
+ * frames from the image stream to be sent to the SurfaceTexture object rather than to the device's
+ * display.
  *
  * <p>When sampling from the texture one should first transform the texture coordinates using the
  * matrix queried via {@link #getTransformMatrix(float[])}.  The transform matrix may change each
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 8483820..485b38a 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -67,14 +67,14 @@
 
     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 +84,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,30 +157,31 @@
     }
 
     private boolean selectTransition(int toIndex) {
-        if (toIndex == mAnimToIndex) {
+        if (toIndex == mTransitionToIndex) {
             // Already animating to that keyframe.
             return true;
         }
 
-        if (mAnim != null) {
-            if (toIndex == mAnimToIndex) {
+        final Transition currentTransition = mTransition;
+        if (currentTransition != null) {
+            if (toIndex == mTransitionToIndex) {
                 return true;
-            } else if (toIndex == mAnimFromIndex) {
+            } else if (toIndex == mTransitionFromIndex) {
                 // Reverse the current animation.
-                mAnim.reverse();
-                mAnimFromIndex = mAnimToIndex;
-                mAnimToIndex = toIndex;
+                currentTransition.reverse();
+                mTransitionFromIndex = mTransitionToIndex;
+                mTransitionToIndex = toIndex;
                 return true;
             }
 
             // Changing animation, end the current animation.
-            mAnim.cancel();
-            mAnim = null;
+            currentTransition.stop();
+            mTransition = null;
         }
 
         // Reset state.
-        mAnimFromIndex = -1;
-        mAnimToIndex = -1;
+        mTransitionFromIndex = -1;
+        mTransitionToIndex = -1;
 
         final AnimatedStateListState state = mState;
         final int fromIndex = getCurrentIndex();
@@ -205,49 +199,144 @@
             return false;
         }
 
+        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 true;
+        }
+
+        @Override
+        public void start() {
+            if (mReversed) {
+                mAvd.reverse();
+            } else {
+                mAvd.start();
+            }
+        }
+
+        @Override
+        public void reverse() {
+            mAvd.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;
         }
     }
 
@@ -407,11 +496,11 @@
     private final AnimatorListenerAdapter mAnimListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator anim) {
-            selectDrawable(mAnimToIndex);
+            selectDrawable(mTransitionToIndex);
 
-            mAnimToIndex = -1;
-            mAnimFromIndex = -1;
-            mAnim = null;
+            mTransitionToIndex = -1;
+            mTransitionFromIndex = -1;
+            mTransition = null;
         }
     };
 
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 1cecef3..071d8ba4 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;
@@ -209,6 +210,7 @@
                     if (drawableRes != 0) {
                         mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable(
                                 drawableRes, theme).mutate();
+                        mAnimatedVectorState.mVectorDrawable.setAllowCaching(false);
                     }
                     a.recycle();
                 } else if (TARGET.equals(tagName)) {
@@ -258,6 +260,7 @@
                 mChangingConfigurations = copy.mChangingConfigurations;
                 // TODO: Make sure the constant state are handled correctly.
                 mVectorDrawable = new VectorDrawable();
+                mVectorDrawable.setAllowCaching(false);
                 mAnimators = new ArrayList<Animator>();
             }
         }
@@ -329,4 +332,20 @@
             animator.pause();
         }
     }
+
+    /**
+     * Reverses ongoing animations or starts pending animations in reverse.
+     * <p>
+     * NOTE: Only works of all animations are ValueAnimators.
+     */
+    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 instanceof ValueAnimator) {
+                ((ValueAnimator) animator).reverse();
+            }
+        }
+    }
 }
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/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 24bbf7c..c0110c9 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -16,6 +16,7 @@
 
 package android.graphics.drawable;
 
+import android.annotation.NonNull;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
@@ -26,6 +27,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.Insets;
 import android.graphics.NinePatch;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
@@ -281,14 +283,35 @@
         return false;
     }
 
+    @Override
+    public boolean getOutline(@NonNull Outline outline) {
+        final Rect bounds = getBounds();
+        if (bounds.isEmpty()) return false;
+
+        if (mNinePatchState != null) {
+            NinePatch.InsetStruct insets = mNinePatchState.getBitmap().getNinePatchInsets();
+            if (insets != null) {
+                final Rect outlineInsets = insets.outlineRect;
+                outline.setRoundRect(bounds.left + outlineInsets.left,
+                        bounds.top + outlineInsets.top,
+                        bounds.right - outlineInsets.right,
+                        bounds.bottom - outlineInsets.bottom,
+                        insets.outlineRadius);
+                outline.setFilled(insets.outlineFilled);
+                return true;
+            }
+        }
+        return super.getOutline(outline);
+    }
+
     /**
      * @hide
      */
     @Override
     public Insets getOpticalInsets() {
         if (needsMirroring()) {
-            return Insets.of(mOpticalInsets.right, mOpticalInsets.top, mOpticalInsets.right,
-                    mOpticalInsets.bottom);
+            return Insets.of(mOpticalInsets.right, mOpticalInsets.top,
+                    mOpticalInsets.left, mOpticalInsets.bottom);
         } else {
             return mOpticalInsets;
         }
@@ -574,7 +597,7 @@
         }
 
         NinePatchState(NinePatch ninePatch, Rect padding) {
-            this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false);
+            this(ninePatch, padding, null, DEFAULT_DITHER, false);
         }
 
         NinePatchState(NinePatch ninePatch, Rect padding, Rect opticalInsets) {
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/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 8783994..8c907b2 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -18,6 +18,7 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
@@ -142,6 +143,10 @@
 
     private boolean mMutated;
 
+    // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise,
+    // caching the bitmap by default is allowed.
+    private boolean mAllowCaching = true;
+
     public VectorDrawable() {
         mVectorState = new VectorDrawableState();
     }
@@ -183,7 +188,23 @@
         final int saveCount = canvas.save();
         final Rect bounds = getBounds();
         canvas.translate(bounds.left, bounds.top);
-        mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
+
+        if (!mAllowCaching) {
+            mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
+        } else {
+            Bitmap bitmap = mVectorState.mCachedBitmap;
+            if (bitmap == null || !mVectorState.canReuseCache(bounds.width(),
+                    bounds.height())) {
+                bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(),
+                        Bitmap.Config.ARGB_8888);
+                Canvas tmpCanvas = new Canvas(bitmap);
+                mVectorState.mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height());
+                mVectorState.mCachedBitmap = bitmap;
+
+                mVectorState.updateCacheStates();
+            }
+            canvas.drawBitmap(bitmap, null, bounds, null);
+        }
         canvas.restoreToCount(saveCount);
     }
 
@@ -444,6 +465,10 @@
         return super.getChangingConfigurations() | mVectorState.mChangingConfigurations;
     }
 
+    void setAllowCaching(boolean allowCaching) {
+        mAllowCaching = allowCaching;
+    }
+
     private static class VectorDrawableState extends ConstantState {
         int[] mThemeAttrs;
         int mChangingConfigurations;
@@ -451,6 +476,12 @@
         ColorStateList mTint;
         Mode mTintMode;
 
+        Bitmap mCachedBitmap;
+        int[] mCachedThemeAttrs;
+        ColorStateList mCachedTint;
+        Mode mCachedTintMode;
+        int mCachedRootAlpha;
+
         // Deep copy for mutate() or implicitly mutate.
         public VectorDrawableState(VectorDrawableState copy) {
             if (copy != null) {
@@ -462,6 +493,27 @@
             }
         }
 
+        public boolean canReuseCache(int width, int height) {
+            if (mCachedThemeAttrs == mThemeAttrs
+                    && mCachedTint == mTint
+                    && mCachedTintMode == mTintMode
+                    && width == mCachedBitmap.getWidth()
+                    && height == mCachedBitmap.getHeight()
+                    && mCachedRootAlpha == mVPathRenderer.getRootAlpha())  {
+                return true;
+            }
+            return false;
+        }
+
+        public void updateCacheStates() {
+            // Use shallow copy here and shallow comparison in canReuseCache(),
+            // likely hit cache miss more, but practically not much difference.
+            mCachedThemeAttrs = mThemeAttrs;
+            mCachedTint = mTint;
+            mCachedTintMode = mTintMode;
+            mCachedRootAlpha = mVPathRenderer.getRootAlpha();
+        }
+
         public VectorDrawableState() {
             mVPathRenderer = new VPathRenderer();
         }
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/AudioManager.java b/media/java/android/media/AudioManager.java
index eb6bf2c..c65961d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2912,6 +2912,20 @@
     }
 
     /**
+     * Notify audio manager about volume controller visibility changes.
+     * Currently limited to SystemUI.
+     *
+     * @hide
+     */
+    public void notifyVolumeControllerVisible(IVolumeController controller, boolean visible) {
+        try {
+            getService().notifyVolumeControllerVisible(controller, visible);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error notifying about volume controller visibility", e);
+        }
+    }
+
+    /**
      * Only useful for volume controllers.
      * @hide
      */
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index b08d631..ab63145 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -61,6 +61,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.Vibrator;
@@ -810,6 +811,9 @@
 
         // Restore the default media button receiver from the system settings
         mMediaFocusControl.restoreMediaButtonReceiver();
+
+        // Load settings for the volume controller
+        mVolumeController.loadSettings(cr);
     }
 
     private int rescaleIndex(int index, int srcStream, int dstStream) {
@@ -851,14 +855,23 @@
         } else {
             streamType = getActiveStreamType(suggestedStreamType);
         }
+        final int resolvedStream = mStreamVolumeAlias[streamType];
 
         // Play sounds on STREAM_RING and STREAM_REMOTE_MUSIC only.
         if ((streamType != STREAM_REMOTE_MUSIC) &&
                 (flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
-                (mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)) {
+                resolvedStream != AudioSystem.STREAM_RING) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
 
+        // For notifications/ring, show the ui before making any adjustments
+        if (mVolumeController.suppressAdjustment(resolvedStream, flags)) {
+            direction = 0;
+            flags &= ~AudioManager.FLAG_PLAY_SOUND;
+            flags &= ~AudioManager.FLAG_VIBRATE;
+            if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
+        }
+
         if (streamType == STREAM_REMOTE_MUSIC) {
             // TODO bounce it to MediaSessionService to find an appropriate
             // session
@@ -4955,15 +4968,65 @@
             }
         }
         mVolumeController.setController(controller);
+        if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
+    }
+
+    @Override
+    public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
+        enforceSelfOrSystemUI("notify about volume controller visibility");
+
+        // return early if the controller is not current
+        if (!mVolumeController.isSameBinder(controller)) {
+            return;
+        }
+
+        mVolumeController.setVisible(visible);
+        if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible);
     }
 
     public static class VolumeController {
         private static final String TAG = "VolumeController";
 
         private IVolumeController mController;
+        private boolean mVisible;
+        private long mNextLongPress;
+        private int mLongPressTimeout;
 
         public void setController(IVolumeController controller) {
             mController = controller;
+            mVisible = false;
+        }
+
+        public void loadSettings(ContentResolver cr) {
+            mLongPressTimeout = Settings.Secure.getIntForUser(cr,
+                    Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
+        }
+
+        public boolean suppressAdjustment(int resolvedStream, int flags) {
+            boolean suppress = false;
+            if (resolvedStream == AudioSystem.STREAM_RING && mController != null) {
+                final long now = SystemClock.uptimeMillis();
+                if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
+                    // ui will become visible
+                    if (mNextLongPress < now) {
+                        mNextLongPress = now + mLongPressTimeout;
+                    }
+                    suppress = true;
+                } else if (mNextLongPress > 0) {  // in a long-press
+                    if (now > mNextLongPress) {
+                        // long press triggered, no more suppression
+                        mNextLongPress = 0;
+                    } else {
+                        // keep suppressing until the long press triggers
+                        suppress = true;
+                    }
+                }
+            }
+            return suppress;
+        }
+
+        public void setVisible(boolean visible) {
+            mVisible = visible;
         }
 
         public boolean isSameBinder(IVolumeController controller) {
@@ -4980,7 +5043,7 @@
 
         @Override
         public String toString() {
-            return "VolumeController(" + asBinder() + ")";
+            return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")";
         }
 
         public void postDisplaySafeVolumeWarning(int flags) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e112a65..4f7021e 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -195,6 +195,8 @@
 
     void setVolumeController(in IVolumeController controller);
 
+    void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible);
+
     boolean isStreamAffectedByRingerMode(int streamType);
 
     void disableSafeMediaVolume();
diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java
index 7a520fe..2f4d136 100644
--- a/media/java/android/media/MediaActionSound.java
+++ b/media/java/android/media/MediaActionSound.java
@@ -24,13 +24,15 @@
  * <p>A class for producing sounds that match those produced by various actions
  * taken by the media and camera APIs.  </p>
  *
- * <p>Use this class to play an appropriate camera operation sound when
- * implementing a custom still or video recording mechanism (through the Camera
- * preview callbacks with {@link android.hardware.Camera#setPreviewCallback
- * Camera.setPreviewCallback}, or through GPU processing with {@link
- * android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
- * example), or when implementing some other camera-like function in your
- * application.</p>
+ * <p>This class is recommended for use with the {@link android.hardware.camera2} API, since the
+ * camera2 API does not play any sounds on its own for any capture or video recording actions.</p>
+ *
+ * <p>With the older {@link android.hardware.Camera} API, use this class to play an appropriate
+ * camera operation sound when implementing a custom still or video recording mechanism (through the
+ * Camera preview callbacks with
+ * {@link android.hardware.Camera#setPreviewCallback Camera.setPreviewCallback}, or through GPU
+ * processing with {@link android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
+ * example), or when implementing some other camera-like function in your application.</p>
  *
  * <p>There is no need to play sounds when using
  * {@link android.hardware.Camera#takePicture Camera.takePicture} or
@@ -136,10 +138,12 @@
      * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
      * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
      *
-     * <p>Using this method makes it easy to match the default device sounds
-     * when recording or capturing data through the preview callbacks, or when
-     * implementing custom camera-like features in your
-     * application.</p>
+     * <p>With the {@link android.hardware.camera2 camera2} API, this method can be used to play
+     * standard camera operation sounds with the appropriate system behavior for such sounds.</p>
+
+     * <p>With the older {@link android.hardware.Camera} API, using this method makes it easy to
+     * match the default device sounds when recording or capturing data through the preview
+     * callbacks, or when implementing custom camera-like features in your application.</p>
      *
      * <p>If the sound has not been loaded by {@link #load} before calling play,
      * play will load the sound at the cost of some additional latency before
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index cb9776a..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}.
@@ -1157,6 +1181,9 @@
      * @deprecated Use the new {@link #getInputBuffer} method instead
      * each time an input buffer is dequeued.
      *
+     * <b>Note:</b>As of API 21, dequeued input buffers are
+     * automatically {@link java.nio.Buffer#clear cleared}.
+     *
      * @throws IllegalStateException if not in the Executing state.
      * @throws MediaCodec.CodecException upon codec error.
      */
@@ -1180,6 +1207,10 @@
      * each time an output buffer is dequeued.  This method is not
      * supported if codec is configured in asynchronous mode.
      *
+     * <b>Note:</b>As of API 21, the position and limit of output
+     * buffers that are dequeued will be set to the valid data
+     * range.
+     *
      * @throws IllegalStateException if not in the Executing state,
      *         or codec is configured in asynchronous mode.
      * @throws MediaCodec.CodecException upon codec error.
@@ -1213,8 +1244,8 @@
     }
 
     /**
-     * Returns a cleared, writable ByteBuffer object for a dequeued
-     * input buffer index to contain the input data.
+     * Returns a {@link java.nio.Buffer#clear cleared}, writable ByteBuffer
+     * object for a dequeued input buffer index to contain the input data.
      *
      * After calling this method any ByteBuffer or Image object
      * previously returned for the same input index MUST no longer
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index f2753ee..b23b540 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -297,8 +297,12 @@
 
     /**
      * Retrieve the current encoded sample and store it in the byte buffer
-     * starting at the given offset. Returns the sample size (or -1 if
-     * no more samples are available).
+     * starting at the given offset.
+     * <p>
+     * <b>Note:</b>As of API 21, on success the position and limit of
+     * {@code byteBuf} is updated to point to the data just read.
+     * @param byteBuf the destination byte buffer
+     * @return the sample size (or -1 if no more samples are available).
      */
     public native int readSampleData(ByteBuffer byteBuf, int offset);
 
@@ -316,7 +320,10 @@
 
     // Keep these in sync with their equivalents in NuMediaExtractor.h
     /**
-     * The sample is a sync sample
+     * The sample is a sync sample (or in {@link MediaCodec}'s terminology
+     * it is a key frame.)
+     *
+     * @see MediaCodec#BUFFER_FLAG_KEY_FRAME
      */
     public static final int SAMPLE_FLAG_SYNC      = 1;
 
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/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index f8c0494..3917bf1 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -114,23 +114,26 @@
     }
 
     /**
-     * Sets a Camera to use for recording. Use this function to switch
-     * quickly between preview and capture mode without a teardown of
-     * the camera object. {@link android.hardware.Camera#unlock()} should be
-     * called before this. Must call before prepare().
+     * Sets a {@link android.hardware.Camera} to use for recording.
+     *
+     * <p>Use this function to switch quickly between preview and capture mode without a teardown of
+     * the camera object. {@link android.hardware.Camera#unlock()} should be called before
+     * this. Must call before {@link #prepare}.</p>
      *
      * @param c the Camera to use for recording
+     * @deprecated Use {@link #getSurface} and the {@link android.hardware.camera2} API instead.
      */
+    @Deprecated
     public native void setCamera(Camera c);
 
     /**
      * Gets the surface to record from when using SURFACE video source.
-     * <p>
-     * Should only be called after prepare(). Frames rendered before start()
-     * will be discarded.
-     * </p>
-     * @throws IllegalStateException if it is called before prepare(), after
-     * stop() or is called when VideoSource is not set to SURFACE.
+     *
+     * <p> May only be called after {@link #prepare}. Frames rendered to the Surface before
+     * {@link #start} will be discarded.</p>
+     *
+     * @throws IllegalStateException if it is called before {@link #prepare}, after
+     * {@link #stop}, or is called when VideoSource is not set to SURFACE.
      * @see android.media.MediaRecorder.VideoSource
      */
     public native Surface getSurface();
@@ -239,7 +242,7 @@
         public static final int DEFAULT = 0;
         /** Camera video source
          * <p>
-         * Using android.hardware.Camera as video source.
+         * Using the {@link android.hardware.Camera} API as video source.
          * </p>
          */
         public static final int CAMERA = 1;
@@ -248,7 +251,7 @@
          * Using a Surface as video source.
          * </p><p>
          * This flag must be used when recording from an
-         * android.hardware.camera2.CameraDevice source.
+         * {@link android.hardware.camera2} API source.
          * </p><p>
          * When using this video source type, use {@link MediaRecorder#getSurface()}
          * to retrieve the surface created by MediaRecorder.
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 73bc61a..740a9d3 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -682,9 +682,13 @@
                 // USE_SESSIONS
                 if (mSession != null) {
                     int pbState = PlaybackState.getStateFromRccState(state);
-                    mSessionPlaybackState.setState(pbState, hasPosition ?
-                            mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN,
-                            playbackSpeed);
+                    long position = hasPosition ? mPlaybackPositionMs
+                            : PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+
+                    PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
+                    bob.setState(pbState, position, playbackSpeed, SystemClock.elapsedRealtime());
+                    bob.setErrorMessage(null);
+                    mSessionPlaybackState = bob.build();
                     mSession.setPlaybackState(mSessionPlaybackState);
                 }
             }
@@ -745,8 +749,9 @@
 
             // USE_SESSIONS
             if (mSession != null) {
-                mSessionPlaybackState.setActions(PlaybackState
-                        .getActionsFromRccControlFlags(transportControlFlags));
+                PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
+                bob.setActions(PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
+                mSessionPlaybackState = bob.build();
                 mSession.setPlaybackState(mSessionPlaybackState);
             }
         }
@@ -946,7 +951,7 @@
     /**
      * Cache for the current playback state using Session APIs.
      */
-    private final PlaybackState mSessionPlaybackState = new PlaybackState();
+    private PlaybackState mSessionPlaybackState = null;
 
     /**
      * Cache for metadata using Session APIs. This is re-initialized in apply().
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 1f5b216..9ea3f26 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -1020,7 +1020,7 @@
                 l.onClientPlaybackStateUpdate(playstate);
             } else {
                 l.onClientPlaybackStateUpdate(playstate, state.getLastPositionUpdateTime(),
-                        state.getPosition(), state.getPlaybackRate());
+                        state.getPosition(), state.getPlaybackSpeed());
             }
             if (state != null) {
                 l.onClientTransportControlUpdate(PlaybackState.getRccControlFlagsFromActions(state
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/routeprovider/IRouteConnection.aidl b/media/java/android/media/routeprovider/IRouteConnection.aidl
deleted file mode 100644
index 15c8039..0000000
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.routeprovider;
-
-import android.media.session.RouteCommand;
-import android.os.ResultReceiver;
-
-/**
- * Interface for a specific connected route.
- * @hide
- */
-oneway interface IRouteConnection {
-    void onCommand(in RouteCommand command, in ResultReceiver cb);
-    void disconnect();
-}
\ No newline at end of file
diff --git a/media/java/android/media/routeprovider/IRouteProvider.aidl b/media/java/android/media/routeprovider/IRouteProvider.aidl
deleted file mode 100644
index c36f6a7..0000000
--- a/media/java/android/media/routeprovider/IRouteProvider.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/* Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.routeprovider;
-
-import android.media.routeprovider.IRouteConnection;
-import android.media.routeprovider.IRouteProviderCallback;
-import android.media.routeprovider.RouteRequest;
-import android.media.session.RouteInfo;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-/**
- * Interface to an app's RouteProviderService.
- * @hide
- */
-oneway interface IRouteProvider {
-    void registerCallback(in IRouteProviderCallback cb);
-    void unregisterCallback(in IRouteProviderCallback cb);
-    void updateDiscoveryRequests(in List<RouteRequest> requests);
-
-    void getAvailableRoutes(in List<RouteRequest> requests, in ResultReceiver cb);
-    void connect(in RouteInfo route, in RouteRequest request, in ResultReceiver cb);
-}
\ No newline at end of file
diff --git a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl b/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
deleted file mode 100644
index 9185347..0000000
--- a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/* Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.routeprovider;
-
-import android.media.routeprovider.IRouteConnection;
-import android.media.session.RouteEvent;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-/**
- * System's provider callback interface.
- * @hide
- */
-oneway interface IRouteProviderCallback {
-    void onRoutesChanged();
-    void onConnectionStateChanged(in IRouteConnection connection, int state);
-    void onConnectionTerminated(in IRouteConnection connection);
-    void onRouteEvent(in RouteEvent event);
-}
\ No newline at end of file
diff --git a/media/java/android/media/routeprovider/RouteConnection.java b/media/java/android/media/routeprovider/RouteConnection.java
deleted file mode 100644
index 43692c1..0000000
--- a/media/java/android/media/routeprovider/RouteConnection.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.routeprovider;
-
-import android.media.routeprovider.IRouteConnection;
-import android.media.session.RouteCommand;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.RouteInterface;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Represents an ongoing connection between an application and a media route
- * offered by a media route provider.
- * <p>
- * The media route provider should add interfaces to the connection before
- * returning it to the system in order to receive commands from clients on those
- * interfaces. Use {@link #addRouteInterface(String)} to add an interface and
- * {@link #getRouteInterface(String)} to retrieve the interface's handle anytime
- * after it has been added.
- * @hide
- */
-public final class RouteConnection {
-    private static final String TAG = "RouteConnection";
-    private final ConnectionStub mBinder;
-    private final ArrayList<String> mIfaceNames = new ArrayList<String>();
-    private final ArrayMap<String, RouteInterfaceHandler> mIfaces
-            = new ArrayMap<String, RouteInterfaceHandler>();
-    private final RouteProviderService mProvider;
-    private final RouteInfo mRoute;
-
-    private boolean mPublished;
-
-    /**
-     * Create a new connection for the given Provider and Route.
-     *
-     * @param provider The provider this route is associated with.
-     * @param route The route this is a connection to.
-     */
-    public RouteConnection(RouteProviderService provider, RouteInfo route) {
-        if (provider == null) {
-            throw new IllegalArgumentException("provider may not be null.");
-        }
-        if (route == null) {
-            throw new IllegalArgumentException("route may not be null.");
-        }
-        mBinder = new ConnectionStub(this);
-        mProvider = provider;
-        mRoute = route;
-    }
-
-    /**
-     * Add an interface to this route connection. All interfaces must be added
-     * to the connection before the connection is returned to the system.
-     *
-     * @param ifaceName The name of the interface to add
-     * @return The route interface that was registered
-     */
-    public RouteInterfaceHandler addRouteInterface(String ifaceName) {
-        if (TextUtils.isEmpty(ifaceName)) {
-            throw new IllegalArgumentException("The interface's name may not be empty");
-        }
-        if (mPublished) {
-            throw new IllegalStateException(
-                    "Connection has already been published to the system.");
-        }
-        RouteInterfaceHandler iface = mIfaces.get(ifaceName);
-        if (iface == null) {
-            iface = new RouteInterfaceHandler(this, ifaceName);
-            mIfaceNames.add(ifaceName);
-            mIfaces.put(ifaceName, iface);
-        } else {
-            Log.w(TAG, "Attempted to add an interface that already exists");
-        }
-        return iface;
-    }
-
-    /**
-     * Get the interface instance for the specified interface name. If the
-     * interface was not added to this connection null will be returned.
-     *
-     * @param ifaceName The name of the interface to get.
-     * @return The route interface with that name or null.
-     */
-    public RouteInterfaceHandler getRouteInterface(String ifaceName) {
-        return mIfaces.get(ifaceName);
-    }
-
-    /**
-     * Close the connection and inform the system that it may no longer be used.
-     */
-    public void shutDown() {
-        mProvider.disconnect(this);
-    }
-
-    /**
-     * @hide
-     */
-    public void sendEvent(String iface, String event, Bundle extras) {
-        RouteEvent e = new RouteEvent(mBinder, iface, event, extras);
-        mProvider.sendRouteEvent(e);
-    }
-
-    /**
-     * @hide
-     */
-    IRouteConnection.Stub getBinder() {
-        return mBinder;
-    }
-
-    /**
-     * @hide
-     */
-    void publish() {
-        mPublished = true;
-    }
-
-    private static class ConnectionStub extends IRouteConnection.Stub {
-        private final WeakReference<RouteConnection> mConnection;
-
-        public ConnectionStub(RouteConnection connection) {
-            mConnection = new WeakReference<RouteConnection>(connection);
-        }
-
-        @Override
-        public void onCommand(RouteCommand command, ResultReceiver cb) {
-            RouteConnection connection = mConnection.get();
-            if (connection != null) {
-                RouteInterfaceHandler iface = connection.mIfaces.get(command.getIface());
-                if (iface != null) {
-                    iface.onCommand(command.getEvent(), command.getExtras(), cb);
-                } else if (cb != null) {
-                    cb.send(RouteInterface.RESULT_INTERFACE_NOT_SUPPORTED, null);
-                }
-            }
-        }
-
-        @Override
-        public void disconnect() {
-            // TODO
-        }
-    }
-}
diff --git a/media/java/android/media/routeprovider/RouteInterfaceHandler.java b/media/java/android/media/routeprovider/RouteInterfaceHandler.java
deleted file mode 100644
index e7f8bbf..0000000
--- a/media/java/android/media/routeprovider/RouteInterfaceHandler.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.routeprovider;
-
-import android.media.session.Route;
-import android.media.session.MediaSession;
-import android.media.session.RouteInterface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Represents an interface that an application may use to send requests to a
- * connected media route.
- * <p>
- * A {@link RouteProviderService} may expose multiple interfaces on a
- * {@link RouteConnection} for a {@link MediaSession} to interact with. A
- * provider creates an interface with
- * {@link RouteConnection#addRouteInterface(String)} to allow messages to be
- * routed appropriately. Events are then sent through a specific interface and
- * all commands being sent on the interface will be sent to any registered
- * {@link CommandListener}s.
- * <p>
- * An interface instance can only be registered on one {@link RouteConnection}.
- * To use the same interface on multiple connections a new instance must be
- * created for each connection.
- * <p>
- * It is recommended you wrap this interface with a standard implementation to
- * avoid errors, but for simple interfaces this class may be used directly. TODO
- * add link to sample code.
- * @hide
- */
-public final class RouteInterfaceHandler {
-    private static final String TAG = "RouteInterfaceHandler";
-
-    private final Object mLock = new Object();
-    private final RouteConnection mConnection;
-    private final String mName;
-
-    private ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
-
-    /**
-     * Create a new RouteInterface for a given connection. This can be used to
-     * send events on the given interface and register listeners for commands
-     * from the connected session.
-     *
-     * @param connection The connection this interface sends events on
-     * @param ifaceName The name of this interface
-     * @hide
-     */
-    public RouteInterfaceHandler(RouteConnection connection, String ifaceName) {
-        if (connection == null) {
-            throw new IllegalArgumentException("connection may not be null");
-        }
-        if (TextUtils.isEmpty(ifaceName)) {
-            throw new IllegalArgumentException("ifaceName can not be empty");
-        }
-        mConnection = connection;
-        mName = ifaceName;
-    }
-
-    /**
-     * Send an event on this interface to the connected session.
-     *
-     * @param event The event to send
-     * @param extras Any extras for the event
-     */
-    public void sendEvent(String event, Bundle extras) {
-        mConnection.sendEvent(mName, event, extras);
-    }
-
-    /**
-     * Send a result from a command to the specified callback. The result codes
-     * in {@link RouteInterface} must be used. More information
-     * about the result, whether successful or an error, should be included in
-     * the extras.
-     *
-     * @param cb The callback to send the result to
-     * @param resultCode The result code for the call
-     * @param extras Any extras to include
-     */
-    public static void sendResult(ResultReceiver cb, int resultCode, Bundle extras) {
-        if (cb != null) {
-            cb.send(resultCode, extras);
-        }
-    }
-
-    /**
-     * Add a listener for this interface. If a handler is specified callbacks
-     * will be performed on the handler's thread, otherwise the callers thread
-     * will be used.
-     *
-     * @param listener The listener to receive calls on.
-     * @param handler The handler whose thread to post calls on or null.
-     */
-    public void addListener(CommandListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener may not be null");
-        }
-        Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
-        synchronized (mLock) {
-            if (findIndexOfListenerLocked(listener) != -1) {
-                Log.d(TAG, "Listener is already added, ignoring");
-                return;
-            }
-            mListeners.add(new MessageHandler(looper, listener));
-        }
-    }
-
-    /**
-     * Remove a listener from this interface.
-     *
-     * @param listener The listener to stop receiving commands on.
-     */
-    public void removeListener(CommandListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener may not be null");
-        }
-        synchronized (mLock) {
-            int index = findIndexOfListenerLocked(listener);
-            if (index != -1) {
-                mListeners.remove(index);
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void onCommand(String command, Bundle args, ResultReceiver cb) {
-        synchronized (mLock) {
-            Command cmd = new Command(command, args, cb);
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).post(MessageHandler.MSG_COMMAND, cmd);
-            }
-        }
-    }
-
-    /**
-     * Get the interface name.
-     *
-     * @return The name of this interface
-     */
-    public String getName() {
-        return mName;
-    }
-
-    private int findIndexOfListenerLocked(CommandListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            MessageHandler handler = mListeners.get(i);
-            if (listener == handler.mListener) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Handles commands sent to the interface.
-     * <p>
-     * Register an InterfaceListener using {@link #addListener}.
-     */
-    public abstract static class CommandListener {
-        /**
-         * This is called when a command is received that matches this
-         * interface. Commands are sent by a {@link MediaSession} that is
-         * connected to the route this interface is registered with.
-         *
-         * @param iface The interface the command was received on.
-         * @param command The command or method to invoke.
-         * @param args Any args that were included with the command. May be
-         *            null.
-         * @param cb The callback provided to send a response on. May be null.
-         * @return true if the command was handled, false otherwise. If the
-         *         command was not handled an error will be sent automatically.
-         *         true may be returned if the command will be handled
-         *         asynchronously.
-         * @see Route
-         * @see MediaSession
-         */
-        public abstract boolean onCommand(RouteInterfaceHandler iface, String command, Bundle args,
-                ResultReceiver cb);
-    }
-
-    private class MessageHandler extends Handler {
-        private static final int MSG_COMMAND = 1;
-
-        private final CommandListener mListener;
-
-        public MessageHandler(Looper looper, CommandListener listener) {
-            super(looper, null, true /* async */);
-            mListener = listener;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_COMMAND:
-                    Command cmd = (Command) msg.obj;
-                    if (!mListener.onCommand(RouteInterfaceHandler.this, cmd.command, cmd.args, cmd.cb)) {
-                        sendResult(cmd.cb, RouteInterface.RESULT_COMMAND_NOT_SUPPORTED,
-                                null);
-                    }
-                    break;
-            }
-        }
-
-        public void post(int what, Object obj) {
-            obtainMessage(what, obj).sendToTarget();
-        }
-    }
-
-    private final static class Command {
-        public final String command;
-        public final Bundle args;
-        public final ResultReceiver cb;
-
-        public Command(String command, Bundle args, ResultReceiver cb) {
-            this.command = command;
-            this.args = args;
-            this.cb = cb;
-        }
-    }
-}
diff --git a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
deleted file mode 100644
index f2c40d2..0000000
--- a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.routeprovider;
-
-import android.media.session.RoutePlaybackControls;
-import android.media.session.RouteInterface;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * Standard wrapper for using playback controls over a {@link RouteInterfaceHandler}.
- * This is the provider half of the interface. Sessions should use
- * {@link RoutePlaybackControls} to interact with this interface.
- * @hide
- */
-public final class RoutePlaybackControlsHandler {
-    private static final String TAG = "RoutePlaybackControls";
-
-    private final RouteInterfaceHandler mIface;
-
-    private RoutePlaybackControlsHandler(RouteInterfaceHandler iface) {
-        mIface = iface;
-    }
-
-    /**
-     * Add this interface to the specified route and return a handle for
-     * communicating on the interface.
-     *
-     * @param connection The connection to register this interface on.
-     * @return A handle for communicating on this interface.
-     */
-    public static RoutePlaybackControlsHandler addTo(RouteConnection connection) {
-        if (connection == null) {
-            throw new IllegalArgumentException("connection may not be null");
-        }
-        RouteInterfaceHandler iface = connection
-                .addRouteInterface(RoutePlaybackControls.NAME);
-
-        return new RoutePlaybackControlsHandler(iface);
-    }
-
-    /**
-     * Add a {@link Listener} to this interface. The listener will receive
-     * commands on the caller's thread.
-     *
-     * @param listener The listener to send commands to.
-     */
-    public void addListener(Listener listener) {
-        addListener(listener, null);
-    }
-
-    /**
-     * Add a {@link Listener} to this interface. The listener will receive
-     * updates on the handler's thread. If no handler is specified the caller's
-     * thread will be used instead.
-     *
-     * @param listener The listener to send commands to.
-     * @param handler The handler whose thread calls should be posted on. May be
-     *            null.
-     */
-    public void addListener(Listener listener, Handler handler) {
-        mIface.addListener(listener, handler);
-    }
-
-    /**
-     * Remove a {@link Listener} from this interface.
-     *
-     * @param listener The Listener to remove.
-     */
-    public void removeListener(Listener listener) {
-        mIface.removeListener(listener);
-    }
-
-    /**
-     * Publish the current playback state to the system and any controllers.
-     * Valid values are defined in {@link PlaybackState}. TODO create
-     * RoutePlaybackState.
-     *
-     * @param state
-     */
-    public void sendPlaybackChangeEvent(int state) {
-        Bundle extras = new Bundle();
-        extras.putInt(RoutePlaybackControls.KEY_VALUE1, state);
-        mIface.sendEvent(RoutePlaybackControls.EVENT_PLAYSTATE_CHANGE, extras);
-    }
-
-    /**
-     * Command handler for the RoutePlaybackControls interface. You can add a
-     * Listener to the interface using {@link #addListener}.
-     */
-    public static abstract class Listener extends RouteInterfaceHandler.CommandListener {
-
-        @Override
-        public final boolean onCommand(RouteInterfaceHandler iface, String method, Bundle extras,
-                ResultReceiver cb) {
-            if (RoutePlaybackControls.CMD_FAST_FORWARD.equals(method)) {
-                boolean success = fastForward();
-                // TODO specify type of error
-                RouteInterfaceHandler.sendResult(cb, success
-                        ? RouteInterface.RESULT_SUCCESS
-                        : RouteInterface.RESULT_ERROR, null);
-                return true;
-            } else if (RoutePlaybackControls.CMD_GET_CURRENT_POSITION.equals(method)) {
-                Bundle result = new Bundle();
-                result.putLong(RoutePlaybackControls.KEY_VALUE1, getCurrentPosition());
-                RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
-                        result);
-                return true;
-            } else if (RoutePlaybackControls.CMD_GET_CAPABILITIES.equals(method)) {
-                Bundle result = new Bundle();
-                result.putLong(RoutePlaybackControls.KEY_VALUE1, getCapabilities());
-                RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
-                        result);
-                return true;
-            } else if (RoutePlaybackControls.CMD_PLAY_NOW.equals(method)) {
-                playNow(extras.getString(RoutePlaybackControls.KEY_VALUE1, null), cb);
-                return true;
-            } else if (RoutePlaybackControls.CMD_RESUME.equals(method)) {
-                boolean success = resume();
-                RouteInterfaceHandler.sendResult(cb, success
-                        ? RouteInterface.RESULT_SUCCESS
-                        : RouteInterface.RESULT_ERROR, null);
-                return true;
-            } else if (RoutePlaybackControls.CMD_PAUSE.equals(method)) {
-                boolean success = pause();
-                RouteInterfaceHandler.sendResult(cb, success
-                        ? RouteInterface.RESULT_SUCCESS
-                        : RouteInterface.RESULT_ERROR, null);
-                return true;
-            } else {
-                // The command wasn't recognized
-            }
-            return false;
-        }
-
-        /**
-         * Override to handle fast forwarding.
-         *
-         * @return true if the request succeeded, false otherwise
-         */
-        public boolean fastForward() {
-            Log.w(TAG, "fastForward is not supported.");
-            return false;
-        }
-
-        /**
-         * Override to handle getting the current position of playback in
-         * millis.
-         *
-         * @return The current position in millis or -1
-         */
-        public long getCurrentPosition() {
-            Log.w(TAG, "getCurrentPosition is not supported");
-            return -1;
-        }
-
-        /**
-         * Override to handle getting the set of capabilities currently
-         * available.
-         *
-         * @return A bit mask of the supported capabilities
-         */
-        public long getCapabilities() {
-            Log.w(TAG, "getCapabilities is not supported");
-            return 0;
-        }
-
-        /**
-         * Override to handle play now requests.
-         *
-         * @param content The uri of the item to play.
-         * @param cb The callback to send the result to.
-         */
-        public void playNow(String content, ResultReceiver cb) {
-            Log.w(TAG, "playNow is not supported");
-            if (cb != null) {
-                // We do this directly since we don't have a reference to the
-                // iface
-                cb.send(RouteInterface.RESULT_COMMAND_NOT_SUPPORTED, null);
-            }
-        }
-
-        /**
-         * Override to handle resume requests. Return true if the call was
-         * handled, even if it was a no-op.
-         *
-         * @return true if the call was handled.
-         */
-        public boolean resume() {
-            Log.w(TAG, "resume is not supported");
-            return false;
-        }
-
-        /**
-         * Override to handle pause requests. Return true if the call was
-         * handled, even if it was a no-op.
-         *
-         * @return true if the call was handled.
-         */
-        public boolean pause() {
-            Log.w(TAG, "pause is not supported");
-            return false;
-        }
-    }
-}
diff --git a/media/java/android/media/routeprovider/RouteProviderService.java b/media/java/android/media/routeprovider/RouteProviderService.java
deleted file mode 100644
index a6ef0bb..0000000
--- a/media/java/android/media/routeprovider/RouteProviderService.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.routeprovider;
-
-import android.app.Service;
-import android.content.Intent;
-import android.media.routeprovider.IRouteProvider;
-import android.media.routeprovider.IRouteProviderCallback;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Base class for defining a route provider service.
- * <p>
- * A route provider offers media routes which represent destinations to which
- * applications may connect, control, and send content. This provides a means
- * for Android applications to interact with a variety of media streaming
- * devices such as speakers or television sets.
- * <p>
- * The system will bind to your provider when an active app is interested in
- * routes that may be discovered through your provider. After binding, the
- * system will send updates on which routes to discover through
- * {@link #updateDiscoveryRequests(List)}. The system will call
- * {@link #getMatchingRoutes(List)} with a subset of filters when a route is
- * needed for a specific app.
- * <p>
- * TODO add documentation for how the sytem knows an app is interested. Maybe
- * interface declarations in the manifest.
- * <p>
- * The system will only start a provider when an app may discover routes through
- * it. If your service needs to run at other times you are responsible for
- * managing its lifecycle.
- * <p>
- * Declare your route provider service in your application manifest like this:
- * <p>
- *
- * <pre>
- *   &lt;service android:name=".MyRouteProviderService"
- *           android:label="@string/my_route_provider_service">
- *       &lt;intent-filter>
- *           &lt;action android:name="com.android.media.session.MediaRouteProvider" />
- *       &lt;/intent-filter>
- *   &lt;/service>
- * </pre>
- * @hide
- */
-public abstract class RouteProviderService extends Service {
-    private static final String TAG = "RouteProvider";
-    /**
-     * A service that implements a RouteProvider must declare that it handles
-     * this action in its AndroidManifest.
-     */
-    public static final String SERVICE_INTERFACE =
-            "com.android.media.session.MediaRouteProvider";
-
-    /**
-     * @hide
-     */
-    public static final String KEY_ROUTES = "routes";
-    /**
-     * @hide
-     */
-    public static final String KEY_CONNECTION = "connection";
-    /**
-     * @hide
-     */
-    public static final int RESULT_FAILURE = -1;
-    /**
-     * @hide
-     */
-    public static final int RESULT_SUCCESS = 0;
-
-    // The system's callback once it has bound to the service
-    private IRouteProviderCallback mCb;
-
-    /**
-     * If your service overrides onBind it must return super.onBind() in
-     * response to the {@link #SERVICE_INTERFACE} action.
-     */
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (intent != null && RouteProviderService.SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mBinder;
-        }
-        return null;
-    }
-
-    /**
-     * Disconnect the specified RouteConnection. The system will stop sending
-     * commands to this connection.
-     *
-     * @param connection The connection to disconnect.
-     * @hide
-     */
-    public final void disconnect(RouteConnection connection) {
-        if (mCb != null) {
-            try {
-                mCb.onConnectionTerminated(connection.getBinder());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error in disconnect.", e);
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public final void sendRouteEvent(RouteEvent event) {
-        if (mCb != null) {
-            try {
-                mCb.onRouteEvent(event);
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Unable to send MediaRouteEvent to system", e);
-            }
-        }
-    }
-
-    /**
-     * Override to handle updates to the routes that are of interest. Each
-     * {@link RouteRequest} will specify if it is an active or passive request.
-     * Route discovery may perform more aggressive discovery on behalf of active
-     * requests but should use low power discovery methods otherwise.
-     * <p>
-     * A single app may have more than one request. Your provider is responsible
-     * for deciding the set of features that are important for discovery given
-     * the set of requests. If your provider only has one method of discovery it
-     * may simply verify that one or more requests are valid before starting
-     * discovery.
-     *
-     * @param requests The route requests that are currently relevant.
-     */
-    public void updateDiscoveryRequests(List<RouteRequest> requests) {
-    }
-
-    /**
-     * Return a list of matching routes for the given set of requests. Returning
-     * null or an empty list indicates there are no matches. A route is
-     * considered matching if it supports one or more of the
-     * {@link RouteOptions} specified. Each returned {@link RouteInfo}
-     * should include all the requested connections that it supports.
-     *
-     * @param options The set of requests for routes
-     * @return The routes that this caller may connect to using one or more of
-     *         the route options.
-     */
-    public abstract List<RouteInfo> getMatchingRoutes(List<RouteRequest> options);
-
-    /**
-     * Handle a request to connect to a specific route with a specific request.
-     * The {@link RouteConnection} must be fully defined before being returned,
-     * though the actual connection to the route may be performed in the
-     * background.
-     *
-     * @param route The route to connect to
-     * @param request The connection request parameters
-     * @return A MediaRouteConnection representing the connection to the route
-     */
-    public abstract RouteConnection connect(RouteInfo route, RouteRequest request);
-
-    private IRouteProvider.Stub mBinder = new IRouteProvider.Stub() {
-
-        @Override
-        public void registerCallback(IRouteProviderCallback cb) throws RemoteException {
-            mCb = cb;
-        }
-
-        @Override
-        public void unregisterCallback(IRouteProviderCallback cb) throws RemoteException {
-            mCb = null;
-        }
-
-        @Override
-        public void updateDiscoveryRequests(List<RouteRequest> requests)
-                throws RemoteException {
-            RouteProviderService.this.updateDiscoveryRequests(requests);
-        }
-
-        @Override
-        public void getAvailableRoutes(List<RouteRequest> requests, ResultReceiver cb)
-                throws RemoteException {
-            List<RouteInfo> routes = RouteProviderService.this.getMatchingRoutes(requests);
-            ArrayList<RouteInfo> routesArray;
-            if (routes instanceof ArrayList) {
-                routesArray = (ArrayList<RouteInfo>) routes;
-            } else {
-                routesArray = new ArrayList<RouteInfo>(routes);
-            }
-            Bundle resultData = new Bundle();
-            resultData.putParcelableArrayList(KEY_ROUTES, routesArray);
-            cb.send(routes == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
-        }
-
-        @Override
-        public void connect(RouteInfo route, RouteRequest request, ResultReceiver cb)
-                throws RemoteException {
-            RouteConnection connection = RouteProviderService.this.connect(route, request);
-            Bundle resultData = new Bundle();
-            if (connection != null) {
-                connection.publish();
-                resultData.putBinder(KEY_CONNECTION, connection.getBinder());
-            }
-
-            cb.send(connection == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
-        }
-    };
-}
diff --git a/media/java/android/media/routeprovider/RouteRequest.aidl b/media/java/android/media/routeprovider/RouteRequest.aidl
deleted file mode 100644
index 7bc5722..0000000
--- a/media/java/android/media/routeprovider/RouteRequest.aidl
+++ /dev/null
@@ -1,18 +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.media.routeprovider;
-
-parcelable RouteRequest;
diff --git a/media/java/android/media/routeprovider/RouteRequest.java b/media/java/android/media/routeprovider/RouteRequest.java
deleted file mode 100644
index 2ba75de..0000000
--- a/media/java/android/media/routeprovider/RouteRequest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.routeprovider;
-
-import android.media.session.RouteOptions;
-import android.media.session.MediaSessionInfo;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.io.PrintWriter;
-
-/**
- * A request to connect or discover routes with certain capabilities.
- * <p>
- * Passed to a {@link RouteProviderService} when a request for discovery or to
- * connect to a route is made. This identifies the app making the request and
- * provides the full set of connection parameters they would like to use for a
- * connection. An app that can connect in multiple ways will be represented by
- * multiple requests.
- * @hide
- */
-public final class RouteRequest implements Parcelable {
-    private final MediaSessionInfo mSessionInfo;
-    private final RouteOptions mOptions;
-    private final boolean mActive;
-
-    /**
-     * @hide
-     */
-    public RouteRequest(MediaSessionInfo info, RouteOptions connRequest,
-            boolean active) {
-        mSessionInfo = info;
-        mOptions = connRequest;
-        mActive = active;
-    }
-
-    private RouteRequest(Parcel in) {
-        mSessionInfo = MediaSessionInfo.CREATOR.createFromParcel(in);
-        mOptions = RouteOptions.CREATOR.createFromParcel(in);
-        mActive = in.readInt() != 0;
-    }
-
-    /**
-     * Get information about the session making the request.
-     *
-     * @return Info on the session making the request
-     */
-    public MediaSessionInfo getSessionInfo() {
-        return mSessionInfo;
-    }
-
-    /**
-     * Get the connection options, which includes the interfaces and other
-     * connection params the session wants to use with a route.
-     *
-     * @return The connection options
-     */
-    public RouteOptions getConnectionOptions() {
-        return mOptions;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder bob = new StringBuilder();
-        bob.append("RouteRequest {");
-        bob.append("active=").append(mActive);
-        bob.append(", info=").append(mSessionInfo.toString());
-        bob.append(", options=").append(mOptions.toString());
-        bob.append("}");
-        return bob.toString();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        mSessionInfo.writeToParcel(dest, flags);
-        mOptions.writeToParcel(dest, flags);
-        dest.writeInt(mActive ? 1 : 0);
-    }
-
-    public static final Parcelable.Creator<RouteRequest> CREATOR
-            = new Parcelable.Creator<RouteRequest>() {
-        @Override
-        public RouteRequest createFromParcel(Parcel in) {
-            return new RouteRequest(in);
-        }
-
-        @Override
-        public RouteRequest[] newArray(int size) {
-            return new RouteRequest[size];
-        }
-    };
-}
diff --git a/media/java/android/media/routing/IMediaRouteClientCallback.aidl b/media/java/android/media/routing/IMediaRouteClientCallback.aidl
new file mode 100644
index 0000000..d90ea3b
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouteClientCallback.aidl
@@ -0,0 +1,41 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.routing;
+
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.ParcelableConnectionInfo;
+import android.media.routing.ParcelableDestinationInfo;
+import android.media.routing.ParcelableRouteInfo;
+import android.os.IBinder;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IMediaRouteClientCallback {
+    void onDestinationFound(int seq, in ParcelableDestinationInfo destination,
+            in ParcelableRouteInfo[] routes);
+
+    void onDestinationLost(int seq, String id);
+
+    void onDiscoveryFailed(int seq, int error, in CharSequence message, in Bundle extras);
+
+    void onConnected(int seq, in ParcelableConnectionInfo connection);
+
+    void onDisconnected(int seq);
+
+    void onConnectionFailed(int seq, int error, in CharSequence message, in Bundle extras);
+}
diff --git a/media/java/android/media/routing/IMediaRouteService.aidl b/media/java/android/media/routing/IMediaRouteService.aidl
new file mode 100644
index 0000000..493ab6d
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouteService.aidl
@@ -0,0 +1,46 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.routing;
+
+import android.media.routing.IMediaRouteClientCallback;
+import android.media.routing.MediaRouteSelector;
+import android.os.Bundle;
+
+/**
+ * Interface to an app's MediaRouteService.
+ * @hide
+ */
+oneway interface IMediaRouteService {
+    void registerClient(int clientUid, String clientPackageName,
+            in IMediaRouteClientCallback callback);
+
+    void unregisterClient(in IMediaRouteClientCallback callback);
+
+    void startDiscovery(in IMediaRouteClientCallback callback, int seq,
+            in List<MediaRouteSelector> selectors, int flags);
+
+    void stopDiscovery(in IMediaRouteClientCallback callback);
+
+    void connect(in IMediaRouteClientCallback callback, int seq,
+            String destinationId, String routeId, int flags, in Bundle extras);
+
+    void disconnect(in IMediaRouteClientCallback callback);
+
+    void pauseStream(in IMediaRouteClientCallback callback);
+
+    void resumeStream(in IMediaRouteClientCallback callback);
+}
+
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl b/media/java/android/media/routing/IMediaRouter.aidl
similarity index 73%
rename from telecomm/java/android/telecomm/CallServiceDescriptor.aidl
rename to media/java/android/media/routing/IMediaRouter.aidl
index f517c73..0abb258 100644
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl
+++ b/media/java/android/media/routing/IMediaRouter.aidl
@@ -1,11 +1,10 @@
-/*
- * Copyright 2014, The Android Open Source Project
+/* Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +13,10 @@
  * limitations under the License.
  */
 
-package android.telecomm;
+package android.media.routing;
 
-parcelable CallServiceDescriptor;
+/** @hide */
+interface IMediaRouter {
+
+}
+
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl b/media/java/android/media/routing/IMediaRouterDelegate.aidl
similarity index 72%
copy from telecomm/java/android/telecomm/CallServiceDescriptor.aidl
copy to media/java/android/media/routing/IMediaRouterDelegate.aidl
index f517c73..35f84c8 100644
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl
+++ b/media/java/android/media/routing/IMediaRouterDelegate.aidl
@@ -1,11 +1,10 @@
-/*
- * Copyright 2014, The Android Open Source Project
+/* Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +13,10 @@
  * limitations under the License.
  */
 
-package android.telecomm;
+package android.media.routing;
 
-parcelable CallServiceDescriptor;
+/** @hide */
+interface IMediaRouterDelegate {
+
+}
+
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
similarity index 72%
copy from telecomm/java/android/telecomm/CallServiceDescriptor.aidl
copy to media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
index f517c73..173ae55 100644
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl
+++ b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
@@ -1,11 +1,10 @@
-/*
- * Copyright 2014, The Android Open Source Project
+/* Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +13,10 @@
  * limitations under the License.
  */
 
-package android.telecomm;
+package android.media.routing;
 
-parcelable CallServiceDescriptor;
+/** @hide */
+interface IMediaRouterRoutingCallback {
+
+}
+
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl b/media/java/android/media/routing/IMediaRouterStateCallback.aidl
similarity index 72%
copy from telecomm/java/android/telecomm/CallServiceDescriptor.aidl
copy to media/java/android/media/routing/IMediaRouterStateCallback.aidl
index f517c73..0299904 100644
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl
+++ b/media/java/android/media/routing/IMediaRouterStateCallback.aidl
@@ -1,11 +1,10 @@
-/*
- * Copyright 2014, The Android Open Source Project
+/* Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +13,10 @@
  * limitations under the License.
  */
 
-package android.telecomm;
+package android.media.routing;
 
-parcelable CallServiceDescriptor;
+/** @hide */
+interface IMediaRouterStateCallback {
+
+}
+
diff --git a/media/java/android/media/session/RouteEvent.aidl b/media/java/android/media/routing/MediaRouteSelector.aidl
similarity index 90%
rename from media/java/android/media/session/RouteEvent.aidl
rename to media/java/android/media/routing/MediaRouteSelector.aidl
index 6966207..37bfa4a 100644
--- a/media/java/android/media/session/RouteEvent.aidl
+++ b/media/java/android/media/routing/MediaRouteSelector.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteEvent;
+parcelable MediaRouteSelector;
diff --git a/media/java/android/media/routing/MediaRouteSelector.java b/media/java/android/media/routing/MediaRouteSelector.java
new file mode 100644
index 0000000..26a9b1c
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouteSelector.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.routing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.routing.MediaRouter.RouteFeatures;
+import android.os.Bundle;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A media route selector consists of a set of constraints that are used to select
+ * the routes to which an application would like to connect.  The constraints consist
+ * of a set of required or optional features and protocols.  The constraints may also
+ * require the use of a specific media route service package or additional characteristics
+ * that are described by a bundle of extra parameters.
+ * <p>
+ * The application will typically create several different selectors that express
+ * various combinations of characteristics that it would like to use together when
+ * it connects to a destination media device.  For each destination that is discovered,
+ * media route services will publish some number of routes and include information
+ * about which selector each route matches.  The application will then choose among
+ * these routes to determine which best satisfies its desired purpose and connect to it.
+ * </p>
+ */
+public final class MediaRouteSelector implements Parcelable {
+    private final int mRequiredFeatures;
+    private final int mOptionalFeatures;
+    private final List<String> mRequiredProtocols;
+    private final List<String> mOptionalProtocols;
+    private final String mServicePackageName;
+    private final Bundle mExtras;
+
+    MediaRouteSelector(int requiredFeatures, int optionalFeatures,
+            List<String> requiredProtocols, List<String> optionalProtocols,
+            String servicePackageName, Bundle extras) {
+        mRequiredFeatures = requiredFeatures;
+        mOptionalFeatures = optionalFeatures;
+        mRequiredProtocols = requiredProtocols;
+        mOptionalProtocols = optionalProtocols;
+        mServicePackageName = servicePackageName;
+        mExtras = extras;
+    }
+
+    /**
+     * Gets the set of required route features.
+     *
+     * @return A set of required route feature flags.
+     */
+    public @RouteFeatures int getRequiredFeatures() {
+        return mRequiredFeatures;
+    }
+
+    /**
+     * Gets the set of optional route features.
+     *
+     * @return A set of optional route feature flags.
+     */
+    public @RouteFeatures int getOptionalFeatures() {
+        return mOptionalFeatures;
+    }
+
+    /**
+     * Gets the list of route protocols that a route must support in order to be selected.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @return The list of fully qualified route protocol names.
+     */
+    public @NonNull List<String> getRequiredProtocols() {
+        return mRequiredProtocols;
+    }
+
+    /**
+     * Gets the list of optional route protocols that a client may use if they are available.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @return The list of optional fully qualified route protocol names.
+     */
+    public @NonNull List<String> getOptionalProtocols() {
+        return mOptionalProtocols;
+    }
+
+    /**
+     * Returns true if the selector includes a required or optional request for
+     * the specified protocol using its fully qualified class name.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @param clazz The protocol class.
+     * @return True if the protocol was requested.
+     */
+    public boolean containsProtocol(@NonNull Class<?> clazz) {
+        return containsProtocol(clazz.getName());
+    }
+
+    /**
+     * Returns true if the selector includes a required or optional request for
+     * the specified protocol.
+     * <p>
+     * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+     * for more information.
+     * </p>
+     *
+     * @param name The name of the protocol.
+     * @return True if the protocol was requested.
+     */
+    public boolean containsProtocol(@NonNull String name) {
+        return mRequiredProtocols.contains(name)
+                || mOptionalProtocols.contains(name);
+    }
+
+    /**
+     * Gets the package name of a specific media route service that this route selector
+     * requires.
+     *
+     * @return The required media route service package name, or null if none.
+     */
+    public @Nullable String getServicePackageName() {
+        return mServicePackageName;
+    }
+
+    /**
+     * Gets optional extras that may be used to select or configure routes for a
+     * particular purpose.  Some extras may be used by media route services to apply
+     * additional constraints or parameters for the routes to be discovered.
+     *
+     * @return The optional extras, or null if none.
+     */
+    public @Nullable Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public String toString() {
+        return "MediaRouteSelector{ "
+                + ", requiredFeatures=0x" + Integer.toHexString(mRequiredFeatures)
+                + ", optionalFeatures=0x" + Integer.toHexString(mOptionalFeatures)
+                + ", requiredProtocols=" + mRequiredProtocols
+                + ", optionalProtocols=" + mOptionalProtocols
+                + ", servicePackageName=" + mServicePackageName
+                + ", extras=" + mExtras + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRequiredFeatures);
+        dest.writeInt(mOptionalFeatures);
+        dest.writeStringList(mRequiredProtocols);
+        dest.writeStringList(mOptionalProtocols);
+        dest.writeString(mServicePackageName);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Parcelable.Creator<MediaRouteSelector> CREATOR =
+            new Parcelable.Creator<MediaRouteSelector>() {
+        @Override
+        public MediaRouteSelector createFromParcel(Parcel source) {
+            int requiredFeatures = source.readInt();
+            int optionalFeatures = source.readInt();
+            ArrayList<String> requiredProtocols = new ArrayList<String>();
+            ArrayList<String> optionalProtocols = new ArrayList<String>();
+            source.readStringList(requiredProtocols);
+            source.readStringList(optionalProtocols);
+            return new MediaRouteSelector(requiredFeatures, optionalFeatures,
+                    requiredProtocols, optionalProtocols,
+                    source.readString(), source.readBundle());
+        }
+
+        @Override
+        public MediaRouteSelector[] newArray(int size) {
+            return new MediaRouteSelector[size];
+        }
+    };
+
+    /**
+     * Builder for {@link MediaRouteSelector} objects.
+     */
+    public static final class Builder {
+        private int mRequiredFeatures;
+        private int mOptionalFeatures;
+        private final ArrayList<String> mRequiredProtocols = new ArrayList<String>();
+        private final ArrayList<String> mOptionalProtocols = new ArrayList<String>();
+        private String mServicePackageName;
+        private Bundle mExtras;
+
+        /**
+         * Creates an initially empty selector builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Sets the set of required route features.
+         *
+         * @param features A set of required route feature flags.
+         */
+        public @NonNull Builder setRequiredFeatures(@RouteFeatures int features) {
+            mRequiredFeatures = features;
+            return this;
+        }
+
+        /**
+         * Sets the set of optional route features.
+         *
+         * @param features A set of optional route feature flags.
+         */
+        public @NonNull Builder setOptionalFeatures(@RouteFeatures int features) {
+            mOptionalFeatures = features;
+            return this;
+        }
+
+        /**
+         * Adds a route protocol that a route must support in order to be selected
+         * using its fully qualified class name.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param clazz The protocol class.
+         * @return this
+         */
+        public @NonNull Builder addRequiredProtocol(@NonNull Class<?> clazz) {
+            if (clazz == null) {
+                throw new IllegalArgumentException("clazz must not be null");
+            }
+            return addRequiredProtocol(clazz.getName());
+        }
+
+        /**
+         * Adds a route protocol that a route must support in order to be selected.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param name The fully qualified name of the required protocol.
+         * @return this
+         */
+        public @NonNull Builder addRequiredProtocol(@NonNull String name) {
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name must not be null or empty");
+            }
+            mRequiredProtocols.add(name);
+            return this;
+        }
+
+        /**
+         * Adds an optional route protocol that a client may use if available
+         * using its fully qualified class name.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param clazz The protocol class.
+         * @return this
+         */
+        public @NonNull Builder addOptionalProtocol(@NonNull Class<?> clazz) {
+            if (clazz == null) {
+                throw new IllegalArgumentException("clazz must not be null");
+            }
+            return addOptionalProtocol(clazz.getName());
+        }
+
+        /**
+         * Adds an optional route protocol that a client may use if available.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         *
+         * @param name The fully qualified name of the optional protocol.
+         * @return this
+         */
+        public @NonNull Builder addOptionalProtocol(@NonNull String name) {
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name must not be null or empty");
+            }
+            mOptionalProtocols.add(name);
+            return this;
+        }
+
+        /**
+         * Sets the package name of the media route service to which this selector
+         * appertains.
+         * <p>
+         * If a package name is specified here then this selector will only be
+         * passed to media route services from that package.  This has the effect
+         * of restricting the set of matching routes to just those that are offered
+         * by that package.
+         * </p>
+         *
+         * @param packageName The required service package name, or null if none.
+         * @return this
+         */
+        public @NonNull Builder setServicePackageName(@Nullable String packageName) {
+            mServicePackageName = packageName;
+            return this;
+        }
+
+        /**
+         * Sets optional extras that may be used to select or configure routes for a
+         * particular purpose.  Some extras may be used by route services to specify
+         * additional constraints or parameters for the routes to be discovered.
+         *
+         * @param extras The optional extras, or null if none.
+         * @return this
+         */
+        public @NonNull Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds the {@link MediaRouteSelector} object.
+         *
+         * @return The new media route selector instance.
+         */
+        public @NonNull MediaRouteSelector build() {
+            return new MediaRouteSelector(mRequiredFeatures, mOptionalFeatures,
+                    mRequiredProtocols, mOptionalProtocols, mServicePackageName, mExtras);
+        }
+    }
+}
diff --git a/media/java/android/media/routing/MediaRouteService.java b/media/java/android/media/routing/MediaRouteService.java
new file mode 100644
index 0000000..4d5a8a9
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouteService.java
@@ -0,0 +1,1023 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.routing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.routing.MediaRouter.ConnectionError;
+import android.media.routing.MediaRouter.ConnectionInfo;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.DiscoveryError;
+import android.media.routing.MediaRouter.DiscoveryRequest;
+import android.media.routing.MediaRouter.RouteInfo;
+import android.media.routing.MediaRouter.ServiceMetadata;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Media route services implement strategies for discovering
+ * and establishing connections to media devices and their routes.  These services
+ * are also known as media route providers.
+ * <p>
+ * Each media route service subclass is responsible for enabling applications
+ * and the system to interact with media devices of some kind.
+ * For example, one particular media route service implementation might
+ * offer support for discovering nearby wireless display devices and streaming
+ * video contents to them; another media route service implementation might
+ * offer support for discovering nearby speakers and streaming media appliances
+ * and sending commands to play content on request.
+ * </p><p>
+ * Subclasses must override the {@link #onCreateClientSession} method to return
+ * a {@link ClientSession} object that implements the {@link ClientSession#onStartDiscovery},
+ * {@link ClientSession#onStopDiscovery}, and {@link ClientSession#onConnect} methods
+ * to allow clients to discover and connect to media devices.
+ * </p><p>
+ * This object is not thread-safe.  All callbacks are invoked on the main looper.
+ * </p>
+ *
+ * <h3>Clients</h3>
+ * <p>
+ * The clients of this API are media applications that would like to discover
+ * and connect to media devices.  The client may also be the system, such as
+ * when the user initiates display mirroring via the Cast Screen function.
+ * </p><p>
+ * There may be multiple client sessions active at the same time.  Each client
+ * session can request discovery and connect to routes independently of any
+ * other client.  It is the responsibility of the media route service to maintain
+ * separate state for each client session and to ensure that clients cannot interfere
+ * with one another in harmful ways.
+ * </p><p>
+ * Notwithstanding the requirement to support any number of concurrent client
+ * sessions, the media route service may impose constraints on how many clients
+ * can connect to the same media device in a particular mode at the same time.
+ * In some cases, media devices may support connections from an arbitrary number
+ * of clients simultaneously but often it may be necessary to ensure that only
+ * one client is in control.  When this happens, the media route service should
+ * report a connection error unless the connection request specifies that the
+ * client should take control of the media device (and forcibly disconnect other
+ * clients that may be using it).
+ * </p>
+ *
+ * <h3>Destinations</h3>
+ * <p>
+ * The media devices to which an application may send media content are referred
+ * to in the API as destinations.  Each destination therefore represents a single
+ * independent device such as a speaker or TV set.  Destinations are given meaningful
+ * names and descriptions to help the user associate them with devices in their
+ * environment.
+ * </p><p>
+ * Destinations may be local or remote and may be accessed through various means,
+ * often wirelessly.  The user may install media route services to enable
+ * media applications to connect to a variety of destinations with different
+ * capabilities.
+ * </p>
+ *
+ * <h3>Routes</h3>
+ * <p>
+ * Routes represent possible usages or means of reaching and interacting with
+ * a destination.  Since destinations may support many different features, they may
+ * each offer multiple routes for applications to choose from based on their needs.
+ * For example, one route might express the ability to stream locally rendered audio
+ * and video to the device; another route might express the ability to send a URL for
+ * the destination to download from the network and play all by itself.
+ * </p><p>
+ * Routes are discovered according to the set of capabilities that
+ * an application or the system is seeking to use at a particular time.  For example,
+ * if an application wants to stream music to a destination then it will ask the
+ * {@link MediaRouter} to find routes to destinations can stream music and ignore
+ * all other destinations that cannot.
+ * </p><p>
+ * In general, the application will inspect the set of routes that have been
+ * offered then connect to the most appropriate route for its desired purpose.
+ * </p>
+ *
+ * <h3>Discovery</h3>
+ * <p>
+ * Discovery is the process of finding destinations based on a description of the
+ * kinds of routes that an application or the system would like to use.
+ * </p><p>
+ * Discovery begins when {@link ClientSession#onStartDiscovery} is called and ends when
+ * {@link ClientSession#onStopDiscovery} is called.  There may be multiple simultaneous
+ * discovery requests in progress at the same time from different clients.  It is up to
+ * the media route service to perform these requests in parallel or multiplex them
+ * as required.
+ * </p><p>
+ * Media route services are <em>strongly encouraged</em> to use the information
+ * in the discovery request to optimize discovery and avoid redundant work.
+ * In the case where no media device supported by the media route service
+ * could possibly offer the requested capabilities, the
+ * {@link ClientSession#onStartDiscovery} method should return <code>false</code> to
+ * let the system know that it can unbind from the media route service and
+ * release its resources.
+ * </p>
+ *
+ * <h3>Settings</h3>
+ * <p>
+ * Many kinds of devices can be discovered on demand simply by scanning the local network
+ * or using wireless protocols such as Bluetooth to find them.  However, in some cases
+ * it may be necessary for the user to manually configure destinations before they
+ * can be used (or to adjust settings later).  Actual user configuration of destinations
+ * is beyond the scope of this API but media route services may specify an activity
+ * in their manifest that the user can launch to perform these tasks.
+ * </p><p>
+ * Note that media route services that are installed from the store must be enabled
+ * by the user before they become available for applications to use.
+ * The {@link android.provider.Settings#ACTION_CAST_SETTINGS Settings.ACTION_CAST_SETTINGS}
+ * settings activity provides the ability for the user to configure media route services.
+ * </p>
+ *
+ * <h3>Manifest Declaration</h3>
+ * <p>
+ * Media route services must be declared in the manifest along with meta-data
+ * about the kinds of routes that they are capable of discovering.  The system
+ * uses this information to optimize the set of services to which it binds in
+ * order to satisfy a particular discovery request.
+ * </p><p>
+ * To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_MEDIA_ROUTE_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action.  You must
+ * also add meta-data to describe the kinds of routes that your service is capable
+ * of discovering.
+ * </p><p>
+ * For example:
+ * </p><pre>
+ * &lt;service android:name=".MediaRouteProvider"
+ *          android:label="&#64;string/service_name"
+ *          android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE">
+ *     &lt;intent-filter>
+ *         &lt;action android:name="android.media.routing.MediaRouteService" />
+ *     &lt;/intent-filter>
+ *
+ *     TODO: INSERT METADATA DECLARATIONS HERE
+ *
+ * &lt;/service>
+ * </pre>
+ */
+public abstract class MediaRouteService extends Service {
+    private static final String TAG = "MediaRouteService";
+
+    private static final boolean DEBUG = true;
+
+    private final Handler mHandler;
+    private final BinderService mService;
+    private final ArrayMap<IBinder, ClientRecord> mClientRecords =
+            new ArrayMap<IBinder, ClientRecord>();
+
+    private ServiceMetadata mMetadata;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "android.media.routing.MediaRouteService";
+
+    /**
+     * Creates a media route service.
+     */
+    public MediaRouteService() {
+        mHandler = new Handler(true);
+        mService = new BinderService();
+    }
+
+    @Override
+    public @Nullable IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mService;
+        }
+        return null;
+    }
+
+    /**
+     * Creates a new client session on behalf of a client.
+     * <p>
+     * The implementation should return a {@link ClientSession} for the client
+     * to use.  The media route service must take care to manage the state of
+     * each client session independently from any others that might also be
+     * in use at the same time.
+     * </p>
+     *
+     * @param client Information about the client.
+     * @return The client session object, or null if the client is not allowed
+     * to interact with this media route service.
+     */
+    public abstract @Nullable ClientSession onCreateClientSession(@NonNull ClientInfo client);
+
+    /**
+     * Gets metadata about this service.
+     * <p>
+     * Use this method to obtain a {@link ServiceMetadata} object to provide when creating
+     * a {@link android.media.routing.MediaRouter.DestinationInfo.Builder}.
+     * </p>
+     *
+     * @return Metadata about this service.
+     */
+    public @NonNull ServiceMetadata getServiceMetadata() {
+        if (mMetadata == null) {
+            try {
+                mMetadata = new ServiceMetadata(this);
+            } catch (NameNotFoundException ex) {
+                Log.wtf(TAG, "Could not retrieve own service metadata!");
+            }
+        }
+        return mMetadata;
+    }
+
+    /**
+     * Enables a single client to access the functionality of the media route service.
+     */
+    public static abstract class ClientSession {
+        /**
+         * Starts discovery.
+         * <p>
+         * If the media route service is capable of discovering routes that satisfy
+         * the request then this method should start discovery and return true.
+         * Otherwise, this method should return false.  If false is returned,
+         * then the framework will not call {@link #onStopDiscovery} since discovery
+         * was never actually started.
+         * </p><p>
+         * There may already be other discovery requests in progress at the same time
+         * for other clients; the media route service must keep track of them all.
+         * </p>
+         *
+         * @param req The discovery request to start.
+         * @param callback A callback to receive discovery events related to this
+         * particular request.  The events that the service sends to this callback
+         * will be sent to the client that initiated the discovery request.
+         * @return True if discovery has started.  False if the media route service
+         * is unable to discover routes that satisfy the request.
+         */
+        public abstract boolean onStartDiscovery(@NonNull DiscoveryRequest req,
+                @NonNull DiscoveryCallback callback);
+
+        /**
+         * Stops discovery.
+         * <p>
+         * If {@link #onStartDiscovery} returned true, then this method will eventually
+         * be called when the framework no longer requires this discovery request
+         * to be performed.
+         * </p><p>
+         * There may still be other discovery requests in progress for other clients;
+         * they must keep working until they have each been stopped by their client.
+         * </p>
+         */
+        public abstract void onStopDiscovery();
+
+        /**
+         * Starts connecting to a route.
+         *
+         * @param req The connection request.
+         * @param callback A callback to receive events connection events related
+         * to this particular request.  The events that the service sends to this callback
+         * will be sent to the client that initiated the discovery request.
+         * @return True if the connection is in progress, or false if the client
+         * unable to connect to the requested route.
+         */
+        public abstract boolean onConnect(@NonNull ConnectionRequest req,
+                @NonNull ConnectionCallback callback);
+
+        /**
+         * Called when the client requests to disconnect from the route
+         * or abort a connection attempt in progress.
+         */
+        public abstract void onDisconnect();
+
+        /**
+         * Called when the client requests to pause streaming of content to
+         * live audio/video routes such as when it goes into the background.
+         * <p>
+         * The default implementation does nothing.
+         * </p>
+         */
+        public void onPauseStream() { }
+
+        /**
+         * Called when the application requests to resume streaming of content to
+         * live audio/video routes such as when it returns to the foreground.
+         * <p>
+         * The default implementation does nothing.
+         * </p>
+         */
+        public void onResumeStream() { }
+
+        /**
+         * Called when the client is releasing the session.
+         * <p>
+         * The framework automatically takes care of stopping discovery and
+         * terminating the connection politely before calling this method to release
+         * the session.
+         * </p><p>
+         * The default implementation does nothing.
+         * </p>
+         */
+        public void onRelease() { }
+    }
+
+    /**
+     * Provides events in response to a discovery request.
+     */
+    public final class DiscoveryCallback {
+        private final ClientRecord mRecord;
+
+        DiscoveryCallback(ClientRecord record) {
+            mRecord = record;
+        }
+
+        /**
+         * Called by the service when a destination is found that
+         * offers one or more routes that satisfy the discovery request.
+         * <p>
+         * This method should be called whenever the list of available routes
+         * at a destination changes or whenever the properties of the destination
+         * itself change.
+         * </p>
+         *
+         * @param destination The destination that was found.
+         * @param routes The list of that destination's routes that satisfy the
+         * discovery request.
+         */
+        public void onDestinationFound(final @NonNull DestinationInfo destination,
+                final @NonNull List<RouteInfo> routes) {
+            if (destination == null) {
+                throw new IllegalArgumentException("destination must not be null");
+            }
+            if (routes == null) {
+                throw new IllegalArgumentException("routes must not be null");
+            }
+            for (int i = 0; i < routes.size(); i++) {
+                if (routes.get(i).getDestination() != destination) {
+                    throw new IllegalArgumentException("routes must refer to the "
+                            + "destination");
+                }
+            }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDestinationFound(DiscoveryCallback.this,
+                            destination, routes);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when a destination is no longer
+         * reachable or is no longer offering any routes that satisfy
+         * the discovery request.
+         *
+         * @param destination The destination that went away.
+         */
+        public void onDestinationLost(final @NonNull DestinationInfo destination) {
+            if (destination == null) {
+                throw new IllegalArgumentException("destination must not be null");
+            }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDestinationLost(DiscoveryCallback.this, destination);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when a discovery has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN},
+         * {@link MediaRouter#DISCOVERY_ERROR_ABORTED},
+         * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onDiscoveryFailed(final @DiscoveryError int error,
+                final @Nullable CharSequence message, final @Nullable Bundle extras) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDiscoveryFailed(DiscoveryCallback.this,
+                            error, message, extras);
+                }
+            });
+        }
+    }
+
+    /**
+     * Provides events in response to a connection request.
+     */
+    public final class ConnectionCallback {
+        private final ClientRecord mRecord;
+
+        ConnectionCallback(ClientRecord record) {
+            mRecord = record;
+        }
+
+        /**
+         * Called by the service when the connection succeeds.
+         *
+         * @param connection Immutable information about the connection.
+         */
+        public void onConnected(final @NonNull ConnectionInfo connection) {
+            if (connection == null) {
+                throw new IllegalArgumentException("connection must not be null");
+            }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchConnected(ConnectionCallback.this, connection);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when the connection is terminated normally.
+         * <p>
+         * Abnormal termination is reported via {@link #onConnectionFailed}.
+         * </p>
+         */
+        public void onDisconnected() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchDisconnected(ConnectionCallback.this);
+                }
+            });
+        }
+
+        /**
+         * Called by the service when a connection attempt or connection in
+         * progress has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#CONNECTION_ERROR_ABORTED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE},
+         * {@link MediaRouter#CONNECTION_ERROR_BUSY},
+         * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT},
+         * {@link MediaRouter#CONNECTION_ERROR_BROKEN},
+         * or {@link MediaRouter#CONNECTION_ERROR_BARGED}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onConnectionFailed(final @ConnectionError int error,
+                final @Nullable CharSequence message, final @Nullable Bundle extras) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecord.dispatchConnectionFailed(ConnectionCallback.this,
+                            error, message, extras);
+                }
+            });
+        }
+    }
+
+    /**
+     * Identifies a client of the media route service.
+     */
+    public static final class ClientInfo {
+        private final int mUid;
+        private final String mPackageName;
+
+        ClientInfo(int uid, String packageName) {
+            mUid = uid;
+            mPackageName = packageName;
+        }
+
+        /**
+         * Gets the UID of the client application.
+         */
+        public int getUid() {
+            return mUid;
+        }
+
+        /**
+         * Gets the package name of the client application.
+         */
+        public @NonNull String getPackageName() {
+            return mPackageName;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ClientInfo{ uid=" + mUid + ", package=" + mPackageName + " }";
+        }
+    }
+
+    private final class BinderService extends IMediaRouteService.Stub {
+        @Override
+        public void registerClient(final int clientUid, final String clientPackageName,
+                final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientInfo client = new ClientInfo(clientUid, clientPackageName);
+                    if (DEBUG) {
+                        Log.d(TAG, "registerClient: client=" + client);
+                    }
+
+                    ClientSession session = onCreateClientSession(client);
+                    if (session == null) {
+                        // request refused by service
+                        Log.w(TAG, "Media route service refused to create session for client: "
+                                + "client=" + client);
+                        return;
+                    }
+
+                    ClientRecord record = new ClientRecord(callback, client, session);
+                    try {
+                        callback.asBinder().linkToDeath(record, 0);
+                    } catch (RemoteException ex) {
+                        // client died prematurely
+                        Log.w(TAG, "Client died prematurely while creating session: "
+                                + "client=" + client);
+                        record.release();
+                        return;
+                    }
+
+                    mClientRecords.put(callback.asBinder(), record);
+                }
+            });
+        }
+
+        @Override
+        public void unregisterClient(IMediaRouteClientCallback callback) {
+            unregisterClient(callback, false);
+        }
+
+        void unregisterClient(final IMediaRouteClientCallback callback,
+                final boolean died) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.remove(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "unregisterClient: client=" + record.getClientInfo()
+                                + ", died=" + died);
+                    }
+
+                    record.release();
+                    callback.asBinder().unlinkToDeath(record, 0);
+                }
+            });
+        }
+
+        @Override
+        public void startDiscovery(final IMediaRouteClientCallback callback,
+                final int seq, final List<MediaRouteSelector> selectors,
+                final int flags) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "startDiscovery: client=" + record.getClientInfo()
+                                + ", seq=" + seq + ", selectors=" + selectors
+                                + ", flags=0x" + Integer.toHexString(flags));
+                    }
+                    record.startDiscovery(seq, selectors, flags);
+                }
+            });
+        }
+
+        @Override
+        public void stopDiscovery(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "stopDiscovery: client=" + record.getClientInfo());
+                    }
+                    record.stopDiscovery();
+                }
+            });
+        }
+
+        @Override
+        public void connect(final IMediaRouteClientCallback callback,
+                final int seq, final String destinationId, final String routeId,
+                final int flags, final Bundle extras) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "connect: client=" + record.getClientInfo()
+                                + ", seq=" + seq + ", destinationId=" + destinationId
+                                + ", routeId=" + routeId
+                                + ", flags=0x" + Integer.toHexString(flags)
+                                + ", extras=" + extras);
+                    }
+                    record.connect(seq, destinationId, routeId, flags, extras);
+                }
+            });
+        }
+
+        @Override
+        public void disconnect(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "disconnect: client=" + record.getClientInfo());
+                    }
+                    record.disconnect();
+                }
+            });
+        }
+
+        @Override
+        public void pauseStream(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "pauseStream: client=" + record.getClientInfo());
+                    }
+                    record.pauseStream();
+                }
+            });
+        }
+
+        @Override
+        public void resumeStream(final IMediaRouteClientCallback callback) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ClientRecord record = mClientRecords.get(callback.asBinder());
+                    if (record == null) {
+                        return; // spurious
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "resumeStream: client=" + record.getClientInfo());
+                    }
+                    record.resumeStream();
+                }
+            });
+        }
+    }
+
+    // Must be accessed on handler
+    private final class ClientRecord implements IBinder.DeathRecipient {
+        private final IMediaRouteClientCallback mClientCallback;
+        private final ClientInfo mClient;
+        private final ClientSession mSession;
+
+        private int mDiscoverySeq;
+        private DiscoveryRequest mDiscoveryRequest;
+        private DiscoveryCallback mDiscoveryCallback;
+        private final ArrayMap<String, DestinationRecord> mDestinations =
+                new ArrayMap<String, DestinationRecord>();
+
+        private int mConnectionSeq;
+        private ConnectionRequest mConnectionRequest;
+        private ConnectionCallback mConnectionCallback;
+        private ConnectionInfo mConnection;
+        private boolean mConnectionPaused;
+
+        public ClientRecord(IMediaRouteClientCallback callback,
+                ClientInfo client, ClientSession session) {
+            mClientCallback = callback;
+            mClient = client;
+            mSession = session;
+        }
+
+        // Invoked on binder thread unlike all other methods in this class.
+        @Override
+        public void binderDied() {
+            mService.unregisterClient(mClientCallback, true);
+        }
+
+        public ClientInfo getClientInfo() {
+            return mClient;
+        }
+
+        public void release() {
+            stopDiscovery();
+            disconnect();
+        }
+
+        public void startDiscovery(int seq, List<MediaRouteSelector> selectors,
+                int flags) {
+            stopDiscovery();
+
+            mDiscoverySeq = seq;
+            mDiscoveryRequest = new DiscoveryRequest(selectors);
+            mDiscoveryRequest.setFlags(flags);
+            mDiscoveryCallback = new DiscoveryCallback(this);
+            boolean started = mSession.onStartDiscovery(mDiscoveryRequest, mDiscoveryCallback);
+            if (!started) {
+                dispatchDiscoveryFailed(mDiscoveryCallback,
+                        MediaRouter.DISCOVERY_ERROR_ABORTED, null, null);
+                clearDiscovery();
+            }
+        }
+
+        public void stopDiscovery() {
+            if (mDiscoveryRequest != null) {
+                mSession.onStopDiscovery();
+                clearDiscovery();
+            }
+        }
+
+        private void clearDiscovery() {
+            mDestinations.clear();
+            mDiscoveryRequest = null;
+            mDiscoveryCallback = null;
+        }
+
+        public void connect(int seq, String destinationId, String routeId,
+                int flags, Bundle extras) {
+            disconnect();
+
+            mConnectionSeq = seq;
+            mConnectionCallback = new ConnectionCallback(this);
+
+            DestinationRecord destinationRecord = mDestinations.get(destinationId);
+            if (destinationRecord == null) {
+                Log.w(TAG, "Aborting connection to route since no matching destination "
+                        + "was found in the list of known destinations: "
+                        + "destinationId=" + destinationId);
+                dispatchConnectionFailed(mConnectionCallback,
+                        MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+                clearConnection();
+                return;
+            }
+
+            RouteInfo route = destinationRecord.getRoute(routeId);
+            if (route == null) {
+                Log.w(TAG, "Aborting connection to route since no matching route "
+                        + "was found in the list of known routes: "
+                        + "destination=" + destinationRecord.destination
+                        + ", routeId=" + routeId);
+                dispatchConnectionFailed(mConnectionCallback,
+                        MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+                clearConnection();
+                return;
+            }
+
+            mConnectionRequest = new ConnectionRequest(route);
+            mConnectionRequest.setFlags(flags);
+            mConnectionRequest.setExtras(extras);
+            boolean started = mSession.onConnect(mConnectionRequest, mConnectionCallback);
+            if (!started) {
+                dispatchConnectionFailed(mConnectionCallback,
+                        MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+                clearConnection();
+            }
+        }
+
+        public void disconnect() {
+            if (mConnectionRequest != null) {
+                mSession.onDisconnect();
+                clearConnection();
+            }
+        }
+
+        private void clearConnection() {
+            mConnectionRequest = null;
+            mConnectionCallback = null;
+            if (mConnection != null) {
+                mConnection.close();
+                mConnection = null;
+            }
+            mConnectionPaused = false;
+        }
+
+        public void pauseStream() {
+            if (mConnectionRequest != null && !mConnectionPaused) {
+                mConnectionPaused = true;
+                mSession.onPauseStream();
+            }
+        }
+
+        public void resumeStream() {
+            if (mConnectionRequest != null && mConnectionPaused) {
+                mConnectionPaused = false;
+                mSession.onResumeStream();
+            }
+        }
+
+        public void dispatchDestinationFound(DiscoveryCallback callback,
+                DestinationInfo destination, List<RouteInfo> routes) {
+            if (callback == mDiscoveryCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "destinationFound: destination=" + destination
+                            + ", routes=" + routes);
+                }
+                mDestinations.put(destination.getId(),
+                        new DestinationRecord(destination, routes));
+
+                ParcelableDestinationInfo pdi = new ParcelableDestinationInfo();
+                pdi.id = destination.getId();
+                pdi.name = destination.getName();
+                pdi.description = destination.getDescription();
+                pdi.iconResourceId = destination.getIconResourceId();
+                pdi.extras = destination.getExtras();
+                ArrayList<ParcelableRouteInfo> pris = new ArrayList<ParcelableRouteInfo>();
+                for (RouteInfo route : routes) {
+                    int selectorIndex = mDiscoveryRequest.getSelectors().indexOf(
+                            route.getSelector());
+                    if (selectorIndex < 0) {
+                        Log.w(TAG, "Ignoring route because the selector does not match "
+                                + "any of those that were originally supplied by the "
+                                + "client's discovery request: destination=" + destination
+                                + ", route=" + route);
+                        continue;
+                    }
+
+                    ParcelableRouteInfo pri = new ParcelableRouteInfo();
+                    pri.id = route.getId();
+                    pri.selectorIndex = selectorIndex;
+                    pri.features = route.getFeatures();
+                    pri.protocols = route.getProtocols().toArray(
+                            new String[route.getProtocols().size()]);
+                    pri.extras = route.getExtras();
+                    pris.add(pri);
+                }
+                try {
+                    mClientCallback.onDestinationFound(mDiscoverySeq, pdi,
+                            pris.toArray(new ParcelableRouteInfo[pris.size()]));
+                } catch (RemoteException ex) {
+                    // binder death handled elsewhere
+                }
+            }
+        }
+
+        public void dispatchDestinationLost(DiscoveryCallback callback,
+                DestinationInfo destination) {
+            if (callback == mDiscoveryCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "destinationLost: destination=" + destination);
+                }
+
+                if (mDestinations.get(destination.getId()).destination == destination) {
+                    mDestinations.remove(destination.getId());
+                    try {
+                        mClientCallback.onDestinationLost(mDiscoverySeq, destination.getId());
+                    } catch (RemoteException ex) {
+                        // binder death handled elsewhere
+                    }
+                }
+            }
+        }
+
+        public void dispatchDiscoveryFailed(DiscoveryCallback callback,
+                int error, CharSequence message, Bundle extras) {
+            if (callback == mDiscoveryCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "discoveryFailed: error=" + error + ", message=" + message
+                            + ", extras=" + extras);
+                }
+
+                try {
+                    mClientCallback.onDiscoveryFailed(mDiscoverySeq, error, message, extras);
+                } catch (RemoteException ex) {
+                    // binder death handled elsewhere
+                }
+            }
+        }
+
+        public void dispatchConnected(ConnectionCallback callback, ConnectionInfo connection) {
+            if (callback == mConnectionCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "connected: connection=" + connection);
+                }
+                if (mConnection == null) {
+                    mConnection = connection;
+
+                    ParcelableConnectionInfo pci = new ParcelableConnectionInfo();
+                    pci.audioAttributes = connection.getAudioAttributes();
+                    pci.presentationDisplayId = connection.getPresentationDisplay() != null ?
+                            connection.getPresentationDisplay().getDisplayId() : -1;
+                    pci.protocolBinders = new IBinder[connection.getProtocols().size()];
+                    for (int i = 0; i < pci.protocolBinders.length; i++) {
+                        pci.protocolBinders[i] = connection.getProtocolBinder(i);
+                    }
+                    pci.extras = connection.getExtras();
+                    try {
+                        mClientCallback.onConnected(mConnectionSeq, pci);
+                    } catch (RemoteException ex) {
+                        // binder death handled elsewhere
+                    }
+                } else {
+                    Log.w(TAG, "Media route service called onConnected() while already "
+                            + "connected.");
+                }
+            }
+        }
+
+        public void dispatchDisconnected(ConnectionCallback callback) {
+            if (callback == mConnectionCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "disconnected");
+                }
+
+                if (mConnection != null) {
+                    mConnection.close();
+                    mConnection = null;
+
+                    try {
+                        mClientCallback.onDisconnected(mConnectionSeq);
+                    } catch (RemoteException ex) {
+                        // binder death handled elsewhere
+                    }
+                }
+            }
+        }
+
+        public void dispatchConnectionFailed(ConnectionCallback callback,
+                int error, CharSequence message, Bundle extras) {
+            if (callback == mConnectionCallback) {
+                if (DEBUG) {
+                    Log.d(TAG, "connectionFailed: error=" + error + ", message=" + message
+                            + ", extras=" + extras);
+                }
+
+                try {
+                    mClientCallback.onConnectionFailed(mConnectionSeq, error, message, extras);
+                } catch (RemoteException ex) {
+                    // binder death handled elsewhere
+                }
+            }
+        }
+    }
+
+    private static final class DestinationRecord {
+        public final DestinationInfo destination;
+        public final List<RouteInfo> routes;
+
+        public DestinationRecord(DestinationInfo destination, List<RouteInfo> routes) {
+            this.destination = destination;
+            this.routes = routes;
+        }
+
+        public RouteInfo getRoute(String routeId) {
+            final int count = routes.size();
+            for (int i = 0; i < count; i++) {
+                RouteInfo route = routes.get(i);
+                if (route.getId().equals(routeId)) {
+                    return route;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/media/java/android/media/routing/MediaRouter.java b/media/java/android/media/routing/MediaRouter.java
new file mode 100644
index 0000000..4f6d324
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouter.java
@@ -0,0 +1,1886 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.routing;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Presentation;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.VolumeProvider;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.Display;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Media router allows applications to discover, connect to, control,
+ * and send content to nearby media devices known as destinations.
+ * <p>
+ * There are generally two participants involved in media routing: an
+ * application that wants to send media content to a destination and a
+ * {@link MediaRouteService media route service} that provides the
+ * service of transporting that content where it needs to go on behalf of the
+ * application.
+ * </p><p>
+ * To send media content to a destination, the application must ask the system
+ * to discover available routes to destinations that provide certain capabilities,
+ * establish a connection to a route, then send messages through the connection to
+ * control the routing of audio and video streams, launch remote applications,
+ * and invoke other functions of the destination.
+ * </p><p>
+ * Media router objects are thread-safe.
+ * </p>
+ *
+ * <h3>Destinations</h3>
+ * <p>
+ * The media devices to which an application may send media content are referred
+ * to in the API as destinations.  Each destination therefore represents a single
+ * independent device such as a speaker or TV set.  Destinations are given meaningful
+ * names and descriptions to help the user associate them with devices in their
+ * environment.
+ * </p><p>
+ * Destinations may be local or remote and may be accessed through various means,
+ * often wirelessly.  The user may install media route services to enable
+ * media applications to connect to a variety of destinations with different
+ * capabilities.
+ * </p>
+ *
+ * <h3>Routes</h3>
+ * <p>
+ * Routes represent possible usages or means of reaching and interacting with
+ * a destination.  Since destinations may support many different features, they may
+ * each offer multiple routes for applications to choose from based on their needs.
+ * For example, one route might express the ability to stream locally rendered audio
+ * and video to the device; another route might express the ability to send a URL for
+ * the destination to download from the network and play all by itself.
+ * </p><p>
+ * Routes are discovered according to the set of capabilities that
+ * an application or the system is seeking to use at a particular time.  For example,
+ * if an application wants to stream music to a destination then it will ask the
+ * {@link MediaRouter} to find routes to destinations can stream music and ignore
+ * all other destinations that cannot.
+ * </p><p>
+ * In general, the application will inspect the set of routes that have been
+ * offered then connect to the most appropriate route for its desired purpose.
+ * </p>
+ *
+ * <h3>Route Selection</h3>
+ * <p>
+ * When the user open the media route chooser activity, the system will display
+ * a list of nearby media destinations which have been discovered.  After the
+ * choice is made the application may connect to one of the routes offered by
+ * this destination and begin communicating with the destination.
+ * </p><p>
+ * Destinations are located through a process called discovery.  During discovery,
+ * the system will start installed {@link MediaRouteService media route services}
+ * to scan the network for nearby devices that offer the kinds of capabilities that the
+ * application is seeking to use.  The application specifies the capabilities it requires by
+ * adding {@link MediaRouteSelector media route selectors} to the media router
+ * using the {@link #addSelector} method.  Only destinations that provide routes
+ * which satisfy at least one of these media route selectors will be discovered.
+ * </p><p>
+ * Once the user has selected a destination, the application will be given a chance
+ * to choose one of the routes to which it would like to connect.  The application
+ * may switch to a different route from the same destination at a later time but
+ * in order to connect to a new destination, the application must once again launch
+ * the media route chooser activity to ask the user to choose a destination.
+ * </p>
+ *
+ * <h3>Route Protocols</h3>
+ * <p>
+ * Route protocols express capabilities offered by routes.  Each media route selector
+ * must specify at least one required protocol by which the routes will be selected.
+ * </p><p>
+ * The framework provides several predefined <code>MediaRouteProtocols</code> which are
+ * defined in the <code>android-support-media-protocols.jar</code> support library.
+ * Applications must statically link this library to make use of these protocols.
+ * </p><p>
+ * The static library approach is used to enable ongoing extension and refinement
+ * of protocols in the SDK and interoperability with the media router implementation
+ * for older platform versions which is offered by the framework support library.
+ * </p><p>
+ * Media route services may also define custom media route protocols of their own
+ * to enable applications to access specialized capabilities of certain destinations
+ * assuming they have linked in the required protocol code.
+ * </p><p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> for more information.
+ * </p>
+ *
+ * <h3>Connections</h3>
+ * <p>
+ * After connecting to a media route, the application can send commands to
+ * the route using any of the protocols that it requested.  If the route supports live
+ * audio or video streaming then the application can create an {@link AudioTrack} or
+ * {@link Presentation} to route locally generated content to the destination.
+ * </p>
+ *
+ * <h3>Delegation</h3>
+ * <p>
+ * The creator of the media router is responsible for establishing the policy for
+ * discovering and connecting to destinations.  UI components may observe the state
+ * of the media router by {@link #createDelegate creating} a {@link Delegate}.
+ * </p><p>
+ * The media router should also be attached to the {@link MediaSession media session}
+ * that is handling media playback lifecycle.  This will allow
+ * authorized {@link MediaController media controllers}, possibly running in other
+ * processes, to provide UI to examine and change the media destination by
+ * {@link MediaController#createMediaRouterDelegate creating} a {@link Delegate}
+ * for the media router associated with the session.
+ * </p>
+ */
+public final class MediaRouter {
+    private final DisplayManager mDisplayManager;
+
+    private final Object mLock = new Object();
+
+    private RoutingCallback mRoutingCallback;
+    private Handler mRoutingCallbackHandler;
+
+    private boolean mReleased;
+    private int mDiscoveryState;
+    private int mConnectionState;
+    private final ArrayList<MediaRouteSelector> mSelectors =
+            new ArrayList<MediaRouteSelector>();
+    private final ArrayMap<DestinationInfo, List<RouteInfo>> mDiscoveredDestinations =
+            new ArrayMap<DestinationInfo, List<RouteInfo>>();
+    private RouteInfo mSelectedRoute;
+    private ConnectionInfo mConnection;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { DISCOVERY_STATE_STOPPED, DISCOVERY_STATE_STARTED })
+    public @interface DiscoveryState { }
+
+    /**
+     * Discovery state: Discovery is not currently in progress.
+     */
+    public static final int DISCOVERY_STATE_STOPPED = 0;
+
+    /**
+     * Discovery state: Discovery is being performed.
+     */
+    public static final int DISCOVERY_STATE_STARTED = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = { DISCOVERY_FLAG_BACKGROUND })
+    public @interface DiscoveryFlags { }
+
+    /**
+     * Discovery flag: Indicates that the client has requested passive discovery in
+     * the background.  The media route service should try to use less power and rely
+     * more on its internal caches to minimize its impact.
+     */
+    public static final int DISCOVERY_FLAG_BACKGROUND = 1 << 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { DISCOVERY_ERROR_UNKNOWN, DISCOVERY_ERROR_ABORTED,
+            DISCOVERY_ERROR_NO_CONNECTIVITY })
+    public @interface DiscoveryError { }
+
+    /**
+     * Discovery error: Unknown error; refer to the error message for details.
+     */
+    public static final int DISCOVERY_ERROR_UNKNOWN = 0;
+
+    /**
+     * Discovery error: The media router or media route service has decided not to
+     * handle the discovery request for some reason.
+     */
+    public static final int DISCOVERY_ERROR_ABORTED = 1;
+
+    /**
+     * Discovery error: The media route service is unable to perform discovery
+     * due to a lack of connectivity such as because the radio is disabled.
+     */
+    public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
+            CONNECTION_STATE_CONNECTED })
+    public @interface ConnectionState { }
+
+    /**
+     * Connection state: No destination has been selected.  Media content should
+     * be sent to the default output.
+     */
+    public static final int CONNECTION_STATE_DISCONNECTED = 0;
+
+    /**
+     * Connection state: The application is in the process of connecting to
+     * a route offered by the selected destination.
+     */
+    public static final int CONNECTION_STATE_CONNECTING = 1;
+
+    /**
+     * Connection state: The application has connected to a route offered by
+     * the selected destination.
+     */
+    public static final int CONNECTION_STATE_CONNECTED = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = { CONNECTION_FLAG_BARGE })
+    public @interface ConnectionFlags { }
+
+    /**
+     * Connection flag: Indicates that the client has requested to barge in and evict
+     * other clients that might have already connected to the destination and that
+     * would otherwise prevent this client from connecting.  When this flag is not
+     * set, the media route service should be polite and report
+     * {@link MediaRouter#CONNECTION_ERROR_BUSY} in case the destination is
+     * already occupied and cannot accept additional connections.
+     */
+    public static final int CONNECTION_FLAG_BARGE = 1 << 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { CONNECTION_ERROR_UNKNOWN, CONNECTION_ERROR_ABORTED,
+            CONNECTION_ERROR_UNAUTHORIZED, CONNECTION_ERROR_UNAUTHORIZED,
+            CONNECTION_ERROR_BUSY, CONNECTION_ERROR_TIMEOUT, CONNECTION_ERROR_BROKEN })
+    public @interface ConnectionError { }
+
+    /**
+     * Connection error: Unknown error; refer to the error message for details.
+     */
+    public static final int CONNECTION_ERROR_UNKNOWN = 0;
+
+    /**
+     * Connection error: The media router or media route service has decided not to
+     * handle the connection request for some reason.
+     */
+    public static final int CONNECTION_ERROR_ABORTED = 1;
+
+    /**
+     * Connection error: The device has refused the connection from this client.
+     * This error should be avoided because the media route service should attempt
+     * to filter out devices that the client cannot access as it performs discovery
+     * on behalf of that client.
+     */
+    public static final int CONNECTION_ERROR_UNAUTHORIZED = 2;
+
+    /**
+     * Connection error: The device is unreachable over the network.
+     */
+    public static final int CONNECTION_ERROR_UNREACHABLE = 3;
+
+    /**
+     * Connection error: The device is already busy serving another client and
+     * the connection request did not ask to barge in.
+     */
+    public static final int CONNECTION_ERROR_BUSY = 4;
+
+    /**
+     * Connection error: A timeout occurred during connection.
+     */
+    public static final int CONNECTION_ERROR_TIMEOUT = 5;
+
+    /**
+     * Connection error: The connection to the device was severed unexpectedly.
+     */
+    public static final int CONNECTION_ERROR_BROKEN = 6;
+
+    /**
+     * Connection error: The connection was terminated because a different client barged
+     * in and took control of the destination.
+     */
+    public static final int CONNECTION_ERROR_BARGED = 7;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { DISCONNECTION_REASON_APPLICATION_REQUEST,
+            DISCONNECTION_REASON_USER_REQUEST, DISCONNECTION_REASON_ERROR })
+    public @interface DisconnectionReason { }
+
+    /**
+     * Disconnection reason: The application requested disconnection itself.
+     */
+    public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0;
+
+    /**
+     * Disconnection reason: The user requested disconnection.
+     */
+    public static final int DISCONNECTION_REASON_USER_REQUEST = 1;
+
+    /**
+     * Disconnection reason: An error occurred.
+     */
+    public static final int DISCONNECTION_REASON_ERROR = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = { ROUTE_FEATURE_LIVE_AUDIO, ROUTE_FEATURE_LIVE_VIDEO })
+    public @interface RouteFeatures { }
+
+    /**
+     * Route feature: Live audio.
+     * <p>
+     * A route that supports live audio streams audio rendered by the application
+     * to the destination.
+     * </p><p>
+     * To take advantage of live audio routing, the application must render its
+     * media using the audio attributes specified by {@link #getPreferredAudioAttributes}.
+     * </p>
+     *
+     * @see #getPreferredAudioAttributes
+     * @see android.media.AudioAttributes
+     */
+    public static final int ROUTE_FEATURE_LIVE_AUDIO = 1 << 0;
+
+    /**
+     * Route feature: Live video.
+     * <p>
+     * A route that supports live video streams video rendered by the application
+     * to the destination.
+     * </p><p>
+     * To take advantage of live video routing, the application must render its
+     * media to a {@link android.app.Presentation presentation window} on the
+     * display specified by {@link #getPreferredPresentationDisplay}.
+     * </p>
+     *
+     * @see #getPreferredPresentationDisplay
+     * @see android.app.Presentation
+     */
+    public static final int ROUTE_FEATURE_LIVE_VIDEO = 1 << 1;
+
+    /**
+     * Creates a media router.
+     *
+     * @param context The context with which the router is associated.
+     */
+    public MediaRouter(@NonNull Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+    }
+
+    /** @hide */
+    public IMediaRouter getBinder() {
+        // todo
+        return null;
+    }
+
+    /**
+     * Disconnects from the selected destination and releases the media router.
+     * <p>
+     * This method should be called by the application when it no longer requires
+     * the media router to ensure that all bound resources may be cleaned up.
+     * </p>
+     */
+    public void release() {
+        synchronized (mLock) {
+            mReleased = true;
+            // todo
+        }
+    }
+
+    /**
+     * Returns true if the media router has been released.
+     */
+    public boolean isReleased() {
+        synchronized (mLock) {
+            return mReleased;
+        }
+    }
+
+    /**
+     * Gets the current route discovery state.
+     *
+     * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED},
+     * {@link #DISCOVERY_STATE_STARTED}.
+     */
+    public @DiscoveryState int getDiscoveryState() {
+        synchronized (mLock) {
+            return mDiscoveryState;
+        }
+    }
+
+    /**
+     * Gets the current route connection state.
+     *
+     * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED},
+     * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}.
+     */
+    public @ConnectionState int getConnectionState() {
+        synchronized (mLock) {
+            return mConnectionState;
+        }
+    }
+
+    /**
+     * Creates a media router delegate through which the destination of the media
+     * router may be controlled.
+     * <p>
+     * This is the point of entry for UI code that initiates discovery and
+     * connection to routes.
+     * </p>
+     */
+    public @NonNull Delegate createDelegate() {
+        return null; // todo
+    }
+
+    /**
+     * Sets a callback to participate in route discovery, filtering, and connection
+     * establishment.
+     *
+     * @param callback The callback to set, or null if none.
+     * @param handler The handler to receive callbacks, or null to use the current thread.
+     */
+    public void setRoutingCallback(@Nullable RoutingCallback callback,
+            @Nullable Handler handler) {
+        synchronized (mLock) {
+            if (callback == null) {
+                mRoutingCallback = null;
+                mRoutingCallbackHandler = null;
+            } else {
+                mRoutingCallback = callback;
+                mRoutingCallbackHandler = handler != null ? handler : new Handler();
+            }
+        }
+    }
+
+    /**
+     * Adds a media route selector to use to find destinations that have
+     * routes with the specified capabilities during route discovery.
+     */
+    public void addSelector(@NonNull MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        synchronized (mLock) {
+            if (!mSelectors.contains(selector)) {
+                mSelectors.add(selector);
+                // todo
+            }
+        }
+    }
+
+    /**
+     * Removes a media route selector.
+     */
+    public void removeSelector(@NonNull MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        synchronized (mLock) {
+            if (mSelectors.remove(selector)) {
+                // todo
+            }
+        }
+    }
+
+    /**
+     * Removes all media route selectors.
+     * <p>
+     * Note that at least one selector must be added in order to perform discovery.
+     * </p>
+     */
+    public void clearSelectors() {
+        synchronized (mLock) {
+            if (!mSelectors.isEmpty()) {
+                mSelectors.clear();
+                // todo
+            }
+        }
+    }
+
+    /**
+     * Gets a list of all media route selectors to consider during discovery.
+     */
+    public @NonNull List<MediaRouteSelector> getSelectors() {
+        synchronized (mLock) {
+            return new ArrayList<MediaRouteSelector>(mSelectors);
+        }
+    }
+
+    /**
+     * Gets the connection to the currently selected route.
+     *
+     * @return The connection to the currently selected route, or null if not connected.
+     */
+    public @NonNull ConnectionInfo getConnection() {
+        synchronized (mLock) {
+            return mConnection;
+        }
+    }
+
+    /**
+     * Gets the list of discovered destinations.
+     * <p>
+     * This list is only valid while discovery is running and is null otherwise.
+     * </p>
+     *
+     * @return The list of discovered destinations, or null if discovery is not running.
+     */
+    public @NonNull List<DestinationInfo> getDiscoveredDestinations() {
+        synchronized (mLock) {
+            if (mDiscoveryState == DISCOVERY_STATE_STARTED) {
+                return new ArrayList<DestinationInfo>(mDiscoveredDestinations.keySet());
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Gets the list of discovered routes for a particular destination.
+     * <p>
+     * This list is only valid while discovery is running and is null otherwise.
+     * </p>
+     *
+     * @param destination The destination for which to get the list of discovered routes.
+     * @return The list of discovered routes for the destination, or null if discovery
+     * is not running.
+     */
+    public @NonNull List<RouteInfo> getDiscoveredRoutes(@NonNull DestinationInfo destination) {
+        if (destination == null) {
+            throw new IllegalArgumentException("destination must not be null");
+        }
+        synchronized (mLock) {
+            if (mDiscoveryState == DISCOVERY_STATE_STARTED) {
+                List<RouteInfo> routes = mDiscoveredDestinations.get(destination);
+                if (routes != null) {
+                    return new ArrayList<RouteInfo>(routes);
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Gets the destination that has been selected.
+     *
+     * @return The selected destination, or null if disconnected.
+     */
+    public @Nullable DestinationInfo getSelectedDestination() {
+        synchronized (mLock) {
+            return mSelectedRoute != null ? mSelectedRoute.getDestination() : null;
+        }
+    }
+
+    /**
+     * Gets the route that has been selected.
+     *
+     * @return The selected destination, or null if disconnected.
+     */
+    public @Nullable RouteInfo getSelectedRoute() {
+        synchronized (mLock) {
+            return mSelectedRoute;
+        }
+    }
+
+    /**
+     * Gets the preferred audio attributes that should be used to stream live audio content
+     * based on the connected route.
+     * <p>
+     * Use an {@link AudioTrack} to send audio content to the destination with these
+     * audio attributes.
+     * </p><p>
+     * The preferred audio attributes may change when a connection is established but it
+     * will remain constant until disconnected.
+     * </p>
+     *
+     * @return The preferred audio attributes to use.  When connected, returns the
+     * route's audio attributes or null if it does not support live audio streaming.
+     * Otherwise returns audio attributes associated with {@link AudioAttributes#USAGE_MEDIA}.
+     */
+    public @Nullable AudioAttributes getPreferredAudioAttributes() {
+        synchronized (mLock) {
+            if (mConnection != null) {
+                return mConnection.getAudioAttributes();
+            }
+            return new AudioAttributes.Builder()
+                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+                    .build();
+        }
+    }
+
+    /**
+     * Gets the preferred presentation display that should be used to stream live video content
+     * based on the connected route.
+     * <p>
+     * Use a {@link Presentation} to send video content to the destination with this display.
+     * </p><p>
+     * The preferred presentation display may change when a connection is established but it
+     * will remain constant until disconnected.
+     * </p>
+     *
+     * @return The preferred presentation display to use.  When connected, returns
+     * the route's presentation display or null if it does not support live video
+     * streaming.  Otherwise returns the first available
+     * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION presentation display},
+     * such as a mirrored wireless or HDMI display or null if none.
+     */
+    public @Nullable Display getPreferredPresentationDisplay() {
+        synchronized (mLock) {
+            if (mConnection != null) {
+                return mConnection.getPresentationDisplay();
+            }
+            Display[] displays = mDisplayManager.getDisplays(
+                    DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
+            return displays.length != 0 ? displays[0] : null;
+        }
+    }
+
+    /**
+     * Gets the preferred volume provider that should be used to control the volume
+     * of content rendered on the currently selected route.
+     * <p>
+     * The preferred volume provider may change when a connection is established but it
+     * will remain the same until disconnected.
+     * </p>
+     *
+     * @return The preferred volume provider to use, or null if the currently
+     * selected route does not support remote volume adjustment or if the connection
+     * is not yet established.  If no route is selected, returns null to indicate
+     * that system volume control should be used.
+     */
+    public @Nullable VolumeProvider getPreferredVolumeProvider() {
+        synchronized (mLock) {
+            if (mConnection != null) {
+                return mConnection.getVolumeProvider();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Requests to pause streaming of live audio or video routes.
+     * Should be called when the application is going into the background and is
+     * no longer rendering content locally.
+     * <p>
+     * This method does nothing unless a connection has been established.
+     * </p>
+     */
+    public void pauseStream() {
+        // todo
+    }
+
+    /**
+     * Requests to resume streaming of live audio or video routes.
+     * May be called when the application is returning to the foreground and is
+     * about to resume rendering content locally.
+     * <p>
+     * This method does nothing unless a connection has been established.
+     * </p>
+     */
+    public void resumeStream() {
+        // todo
+    }
+
+    /**
+     * This class is used by UI components to let the user discover and
+     * select a destination to which the media router should connect.
+     * <p>
+     * This API has somewhat more limited functionality than the {@link MediaRouter}
+     * itself because it is designed to allow applications to control
+     * the destination of media router instances that belong to other processes.
+     * </p><p>
+     * To control the destination of your own media router, call
+     * {@link #createDelegate} to obtain a local delegate object.
+     * </p><p>
+     * To control the destination of a media router that belongs to another process,
+     * first obtain a {@link MediaController} that is associated with the media playback
+     * that is occurring in that process, then call
+     * {@link MediaController#createMediaRouterDelegate} to obtain an instance of
+     * its destination controls.  Note that special permissions may be required to
+     * obtain the {@link MediaController} instance in the first place.
+     * </p>
+     */
+    public static final class Delegate {
+        /**
+         * Returns true if the media router has been released.
+         */
+        public boolean isReleased() {
+            // todo
+            return false;
+        }
+
+        /**
+         * Gets the current route discovery state.
+         *
+         * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED},
+         * {@link #DISCOVERY_STATE_STARTED}.
+         */
+        public @DiscoveryState int getDiscoveryState() {
+            // todo
+            return -1;
+        }
+
+        /**
+         * Gets the current route connection state.
+         *
+         * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED},
+         * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}.
+         */
+        public @ConnectionState int getConnectionState() {
+            // todo
+            return -1;
+        }
+
+        /**
+         * Gets the currently selected destination.
+         *
+         * @return The destination information, or null if none.
+         */
+        public @Nullable DestinationInfo getSelectedDestination() {
+            return null;
+        }
+
+        /**
+         * Gets the list of discovered destinations.
+         * <p>
+         * This list is only valid while discovery is running and is null otherwise.
+         * </p>
+         *
+         * @return The list of discovered destinations, or null if discovery is not running.
+         */
+        public @NonNull List<DestinationInfo> getDiscoveredDestinations() {
+            return null;
+        }
+
+        /**
+         * Adds a callback to receive state changes.
+         *
+         * @param callback The callback to set, or null if none.
+         * @param handler The handler to receive callbacks, or null to use the current thread.
+         */
+        public void addStateCallback(@Nullable StateCallback callback,
+                @Nullable Handler handler) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback must not be null");
+            }
+            if (handler == null) {
+                handler = new Handler();
+            }
+            // todo
+        }
+
+        /**
+         * Removes a callback for state changes.
+         *
+         * @param callback The callback to set, or null if none.
+         */
+        public void removeStateCallback(@Nullable StateCallback callback) {
+            // todo
+        }
+
+        /**
+         * Starts performing discovery.
+         * <p>
+         * Performing discovery is expensive.  Make sure to call {@link #stopDiscovery}
+         * as soon as possible once a new destination has been selected to allow the system
+         * to stop services associated with discovery.
+         * </p>
+         *
+         * @param flags The discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+         */
+        public void startDiscovery(@DiscoveryFlags int flags) {
+            // todo
+        }
+
+        /**
+         * Stops performing discovery.
+         */
+        public void stopDiscovery() {
+            // todo
+        }
+
+        /**
+         * Connects to a destination during route discovery.
+         * <p>
+         * This method may only be called while route discovery is active and the
+         * destination appears in the
+         * {@link #getDiscoveredDestinations list of discovered destinations}.
+         * If the media router is already connected to a route then it will first disconnect
+         * from the current route then connect to the new route.
+         * </p>
+         *
+         * @param destination The destination to which the media router should connect.
+         * @param flags The connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+         */
+        public void connect(@NonNull DestinationInfo destination, @DiscoveryFlags int flags) {
+            // todo
+        }
+
+        /**
+         * Disconnects from the currently selected destination.
+         * <p>
+         * Does nothing if not currently connected.
+         * </p>
+         *
+         * @param reason The reason for the disconnection: one of
+         * {@link #DISCONNECTION_REASON_APPLICATION_REQUEST},
+         * {@link #DISCONNECTION_REASON_USER_REQUEST}, or {@link #DISCONNECTION_REASON_ERROR}.
+         */
+        public void disconnect(@DisconnectionReason int reason) {
+            // todo
+        }
+    }
+
+    /**
+     * Describes immutable properties of a connection to a route.
+     */
+    public static final class ConnectionInfo {
+        private final RouteInfo mRoute;
+        private final AudioAttributes mAudioAttributes;
+        private final Display mPresentationDisplay;
+        private final VolumeProvider mVolumeProvider;
+        private final IBinder[] mProtocolBinders;
+        private final Object[] mProtocolInstances;
+        private final Bundle mExtras;
+        private final ArrayList<Closeable> mCloseables;
+
+        private static final Class<?>[] MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS =
+                new Class<?>[] { IBinder.class };
+
+        ConnectionInfo(RouteInfo route,
+                AudioAttributes audioAttributes, Display display,
+                VolumeProvider volumeProvider, IBinder[] protocolBinders,
+                Bundle extras, ArrayList<Closeable> closeables) {
+            mRoute = route;
+            mAudioAttributes = audioAttributes;
+            mPresentationDisplay = display;
+            mVolumeProvider = volumeProvider;
+            mProtocolBinders = protocolBinders;
+            mProtocolInstances = new Object[mProtocolBinders.length];
+            mExtras = extras;
+            mCloseables = closeables;
+        }
+
+        /**
+         * Gets the route that is connected.
+         */
+        public @NonNull RouteInfo getRoute() {
+            return mRoute;
+        }
+
+        /**
+         * Gets the audio attributes which the client should use to stream audio
+         * to the destination, or null if the route does not support live audio streaming.
+         */
+        public @Nullable AudioAttributes getAudioAttributes() {
+            return mAudioAttributes;
+        }
+
+        /**
+         * Gets the display which the client should use to stream video to the
+         * destination using a {@link Presentation}, or null if the route does not
+         * support live video streaming.
+         */
+        public @Nullable Display getPresentationDisplay() {
+            return mPresentationDisplay;
+        }
+
+        /**
+         * Gets the route's volume provider, or null if none.
+         */
+        public @Nullable VolumeProvider getVolumeProvider() {
+            return mVolumeProvider;
+        }
+
+        /**
+         * Gets the set of supported route features.
+         */
+        public @RouteFeatures int getFeatures() {
+            return mRoute.getFeatures();
+        }
+
+        /**
+         * Gets the list of supported route protocols.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @NonNull List<String> getProtocols() {
+            return mRoute.getProtocols();
+        }
+
+        /**
+         * Gets an instance of a route protocol object that wraps the protocol binder
+         * and provides easy access to the protocol's functionality.
+         * <p>
+         * This is a convenience method which invokes {@link #getProtocolBinder(String)}
+         * using the name of the provided class then passes the resulting {@link IBinder}
+         * to a single-argument constructor of that class.
+         * </p><p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        @SuppressWarnings("unchecked")
+        public @Nullable <T> T getProtocolObject(Class<T> clazz) {
+            int index = getProtocols().indexOf(clazz.getName());
+            if (index < 0) {
+                return null;
+            }
+            if (mProtocolInstances[index] == null && mProtocolBinders[index] != null) {
+                final Constructor<T> ctor;
+                try {
+                    ctor = clazz.getConstructor(MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS);
+                } catch (NoSuchMethodException ex) {
+                    throw new RuntimeException("Could not find public constructor "
+                            + "with IBinder argument in protocol class: " + clazz.getName(), ex);
+                }
+                try {
+                    mProtocolInstances[index] = ctor.newInstance(mProtocolBinders[index]);
+                } catch (InstantiationException | IllegalAccessException
+                        | InvocationTargetException ex) {
+                    throw new RuntimeException("Could create instance of protocol class: "
+                            + clazz.getName(), ex);
+                }
+            }
+            return (T)mProtocolInstances[index];
+        }
+
+        /**
+         * Gets the {@link IBinder} that provides access to the specified route protocol
+         * or null if the protocol is not supported.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @Nullable IBinder getProtocolBinder(@NonNull String name) {
+            int index = getProtocols().indexOf(name);
+            return index >= 0 ? mProtocolBinders[index] : null;
+        }
+
+        /**
+         * Gets the {@link IBinder} that provides access to the specified route protocol
+         * at the given index in the protocol list or null if the protocol is not supported.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @Nullable IBinder getProtocolBinder(int index) {
+            return mProtocolBinders[index];
+        }
+
+        /**
+         * Gets optional extra media route service or protocol specific information about
+         * the connection.  Use the service or protocol name as the prefix for
+         * any extras to avoid namespace collisions.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Closes all closeables associated with the connection when the connection
+         * is being torn down.
+         */
+        void close() {
+            final int count = mCloseables.size();
+            for (int i = 0; i < count; i++) {
+                try {
+                    mCloseables.get(i).close();
+                } catch (IOException ex) {
+                }
+            }
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ConnectionInfo{ route=" + mRoute
+                    + ", audioAttributes=" + mAudioAttributes
+                    + ", presentationDisplay=" + mPresentationDisplay
+                    + ", volumeProvider=" + mVolumeProvider
+                    + ", protocolBinders=" + mProtocolBinders + " }";
+        }
+
+        /**
+         * Builds {@link ConnectionInfo} objects.
+         */
+        public static final class Builder {
+            private final RouteInfo mRoute;
+            private AudioAttributes mAudioAttributes;
+            private Display mPresentationDisplay;
+            private VolumeProvider mVolumeProvider;
+            private final IBinder[] mProtocols;
+            private Bundle mExtras;
+            private final ArrayList<Closeable> mCloseables = new ArrayList<Closeable>();
+
+            /**
+             * Creates a builder for connection information.
+             *
+             * @param route The route that is connected.
+             */
+            public Builder(@NonNull RouteInfo route) {
+                if (route == null) {
+                    throw new IllegalArgumentException("route");
+                }
+                mRoute = route;
+                mProtocols = new IBinder[route.getProtocols().size()];
+            }
+
+            /**
+             * Sets the audio attributes which the client should use to stream audio
+             * to the destination, or null if the route does not support live audio streaming.
+             */
+            public @NonNull Builder setAudioAttributes(
+                    @Nullable AudioAttributes audioAttributes) {
+                mAudioAttributes = audioAttributes;
+                return this;
+            }
+
+            /**
+             * Sets the display which the client should use to stream video to the
+             * destination using a {@link Presentation}, or null if the route does not
+             * support live video streaming.
+             */
+            public @NonNull Builder setPresentationDisplay(@Nullable Display display) {
+                mPresentationDisplay = display;
+                return this;
+            }
+
+            /**
+             * Sets the route's volume provider, or null if none.
+             */
+            public @NonNull Builder setVolumeProvider(@Nullable VolumeProvider provider) {
+                mVolumeProvider = provider;
+                return this;
+            }
+
+            /**
+             * Sets the binder stub of a supported route protocol using
+             * the protocol's fully qualified class name.  The protocol must be one
+             * of those that was indicated as being supported by the route.
+             * <p>
+             * If the stub implements {@link Closeable} then it will automatically
+             * be closed when the client disconnects from the route.
+             * </p><p>
+             * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+             * for more information.
+             * </p>
+             */
+            public @NonNull Builder setProtocolStub(@NonNull Class<?> clazz,
+                    @NonNull IInterface stub) {
+                if (clazz == null) {
+                    throw new IllegalArgumentException("clazz must not be null");
+                }
+                if (stub == null) {
+                    throw new IllegalArgumentException("stub must not be null");
+                }
+                if (stub instanceof Closeable) {
+                    mCloseables.add((Closeable)stub);
+                }
+                return setProtocolBinder(clazz.getName(), stub.asBinder());
+            }
+
+            /**
+             * Sets the binder interface of a supported route protocol by name.
+             * The protocol must be one of those that was indicated as being supported
+             * by the route.
+             * <p>
+             * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+             * for more information.
+             * </p>
+             */
+            public @NonNull Builder setProtocolBinder(@NonNull String name,
+                    @NonNull IBinder binder) {
+                if (TextUtils.isEmpty(name)) {
+                    throw new IllegalArgumentException("name must not be null or empty");
+                }
+                if (binder == null) {
+                    throw new IllegalArgumentException("binder must not be null");
+                }
+                int index = mRoute.getProtocols().indexOf(name);
+                if (index < 0) {
+                    throw new IllegalArgumentException("name must specify a protocol that "
+                            + "the route actually declared that it supports: "
+                            + "name=" + name + ", protocols=" + mRoute.getProtocols());
+                }
+                mProtocols[index] = binder;
+                return this;
+            }
+
+            /**
+             * Sets optional extra media route service or protocol specific information about
+             * the connection.  Use the service or protocol name as the prefix for
+             * any extras to avoid namespace collisions.
+             */
+            public @NonNull Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds the {@link ConnectionInfo} object.
+             */
+            public @NonNull ConnectionInfo build() {
+                return new ConnectionInfo(mRoute,
+                        mAudioAttributes, mPresentationDisplay,
+                        mVolumeProvider, mProtocols, mExtras, mCloseables);
+            }
+        }
+    }
+
+    /**
+     * Describes one particular way of routing media content to a destination
+     * according to the capabilities specified by a media route selector on behalf
+     * of an application.
+     */
+    public static final class RouteInfo {
+        private final String mId;
+        private final DestinationInfo mDestination;
+        private final MediaRouteSelector mSelector;
+        private final int mFeatures;
+        private final ArrayList<String> mProtocols;
+        private final Bundle mExtras;
+
+        RouteInfo(String id, DestinationInfo destination, MediaRouteSelector selector,
+                int features, ArrayList<String> protocols, Bundle extras) {
+            mId = id;
+            mDestination = destination;
+            mSelector = selector;
+            mFeatures = features;
+            mProtocols = protocols;
+            mExtras = extras;
+        }
+
+        /**
+         * Gets the route's stable identifier.
+         * <p>
+         * The id is intended to uniquely identify the route among all routes that
+         * are offered by a particular destination in such a way that the client can
+         * refer to it at a later time.
+         * </p>
+         */
+        public @NonNull String getId() {
+            return mId;
+        }
+
+        /**
+         * Gets the destination that is offering this route.
+         */
+        public @NonNull DestinationInfo getDestination() {
+            return mDestination;
+        }
+
+        /**
+         * Gets the media route selector provided by the client for which this
+         * route was created.
+         * <p>
+         * It is implied that this route supports all of the required capabilities
+         * that were expressed in the selector.
+         * </p>
+         */
+        public @NonNull MediaRouteSelector getSelector() {
+            return mSelector;
+        }
+
+        /**
+         * Gets the set of supported route features.
+         */
+        public @RouteFeatures int getFeatures() {
+            return mFeatures;
+        }
+
+        /**
+         * Gets the list of supported route protocols.
+         * <p>
+         * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+         * for more information.
+         * </p>
+         */
+        public @NonNull List<String> getProtocols() {
+            return mProtocols;
+        }
+
+        /**
+         * Gets optional extra information about the route, or null if none.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "RouteInfo{ id=" + mId + ", destination=" + mDestination
+                    + ", features=0x" + Integer.toHexString(mFeatures)
+                    + ", selector=" + mSelector + ", protocols=" + mProtocols
+                    + ", extras=" + mExtras + " }";
+        }
+
+        /**
+         * Builds {@link RouteInfo} objects.
+         */
+        public static final class Builder {
+            private final DestinationInfo mDestination;
+            private final String mId;
+            private final MediaRouteSelector mSelector;
+            private int mFeatures;
+            private final ArrayList<String> mProtocols = new ArrayList<String>();
+            private Bundle mExtras;
+
+            /**
+             * Creates a builder for route information.
+             *
+             * @param id The route's stable identifier.
+             * @param destination The destination of this route.
+             * @param selector The media route selector provided by the client for which
+             * this route was created.  This must be one of the selectors that was
+             * included in the discovery request.
+             */
+            public Builder(@NonNull String id, @NonNull DestinationInfo destination,
+                    @NonNull MediaRouteSelector selector) {
+                if (TextUtils.isEmpty(id)) {
+                    throw new IllegalArgumentException("id must not be null or empty");
+                }
+                if (destination == null) {
+                    throw new IllegalArgumentException("destination must not be null");
+                }
+                if (selector == null) {
+                    throw new IllegalArgumentException("selector must not be null");
+                }
+                mDestination = destination;
+                mId = id;
+                mSelector = selector;
+            }
+
+            /**
+             * Sets the set of supported route features.
+             */
+            public @NonNull Builder setFeatures(@RouteFeatures int features) {
+                mFeatures = features;
+                return this;
+            }
+
+            /**
+             * Adds a supported route protocol using its fully qualified class name.
+             * <p>
+             * If the protocol was not requested by the client in its selector
+             * then it will be silently discarded.
+             * </p>
+             */
+            public @NonNull <T extends IInterface> Builder addProtocol(@NonNull Class<T> clazz) {
+                if (clazz == null) {
+                    throw new IllegalArgumentException("clazz must not be null");
+                }
+                return addProtocol(clazz.getName());
+            }
+
+            /**
+             * Adds a supported route protocol by name.
+             * <p>
+             * If the protocol was not requested by the client in its selector
+             * then it will be silently discarded.
+             * </p>
+             */
+            public @NonNull Builder addProtocol(@NonNull String name) {
+                if (TextUtils.isEmpty(name)) {
+                    throw new IllegalArgumentException("name must not be null");
+                }
+                if (mSelector.containsProtocol(name)) {
+                    mProtocols.add(name);
+                }
+                return this;
+            }
+
+            /**
+             * Sets optional extra information about the route, or null if none.
+             */
+            public @NonNull Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds the {@link RouteInfo} object.
+             * <p>
+             * Ensures that all required protocols have been supplied.
+             * </p>
+             */
+            public @NonNull RouteInfo build() {
+                int missingFeatures = mSelector.getRequiredFeatures() & ~mFeatures;
+                if (missingFeatures != 0) {
+                    throw new IllegalStateException("The media route selector "
+                            + "specified required features which this route does "
+                            + "not appear to support so it should not have been published: "
+                            + "missing 0x" + Integer.toHexString(missingFeatures));
+                }
+                for (String protocol : mSelector.getRequiredProtocols()) {
+                    if (!mProtocols.contains(protocol)) {
+                        throw new IllegalStateException("The media route selector "
+                                + "specified required protocols which this route "
+                                + "does not appear to support so it should not have "
+                                + "been published: missing " + protocol);
+                    }
+                }
+                return new RouteInfo(mId, mDestination, mSelector,
+                        mFeatures, mProtocols, mExtras);
+            }
+        }
+    }
+
+    /**
+     * Describes a destination for media content such as a device,
+     * an individual port on a device, or a group of devices.
+     */
+    public static final class DestinationInfo {
+        private final String mId;
+        private final ServiceMetadata mService;
+        private final CharSequence mName;
+        private final CharSequence mDescription;
+        private final int mIconResourceId;
+        private final Bundle mExtras;
+
+        DestinationInfo(String id, ServiceMetadata service,
+                CharSequence name, CharSequence description,
+                int iconResourceId, Bundle extras) {
+            mId = id;
+            mService = service;
+            mName = name;
+            mDescription = description;
+            mIconResourceId = iconResourceId;
+            mExtras = extras;
+        }
+
+        /**
+         * Gets the destination's stable identifier.
+         * <p>
+         * The id is intended to uniquely identify the destination among all destinations
+         * provided by the media route service in such a way that the client can
+         * refer to it at a later time.  Ideally, the id should be resilient to
+         * user-initiated actions such as changes to the name or description
+         * of the destination.
+         * </p>
+         */
+        public @NonNull String getId() {
+            return mId;
+        }
+
+        /**
+         * Gets metadata about the service that is providing access to this destination.
+         */
+        public @NonNull ServiceMetadata getServiceMetadata() {
+            return mService;
+        }
+
+        /**
+         * Gets the destination's name for display to the user.
+         */
+        public @NonNull CharSequence getName() {
+            return mName;
+        }
+
+        /**
+         * Gets the destination's description for display to the user, or null if none.
+         */
+        public @Nullable CharSequence getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Gets an icon resource from the service's package which is used
+         * to identify the destination, or -1 if none.
+         */
+        public @DrawableRes int getIconResourceId() {
+            return mIconResourceId;
+        }
+
+        /**
+         * Loads the icon drawable, or null if none.
+         */
+        public @Nullable Drawable loadIcon(@NonNull PackageManager pm) {
+            return mIconResourceId >= 0 ? mService.getDrawable(pm, mIconResourceId) : null;
+        }
+
+        /**
+         * Gets optional extra information about the destination, or null if none.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "DestinationInfo{ id=" + mId + ", service=" + mService + ", name=" + mName
+                    + ", description=" + mDescription + ", iconResourceId=" + mIconResourceId
+                    + ", extras=" + mExtras + " }";
+        }
+
+        /**
+         * Builds {@link DestinationInfo} objects.
+         */
+        public static final class Builder {
+            private final String mId;
+            private final ServiceMetadata mService;
+            private final CharSequence mName;
+            private CharSequence mDescription;
+            private int mIconResourceId = -1;
+            private Bundle mExtras;
+
+            /**
+             * Creates a builder for destination information.
+             *
+             * @param id The destination's stable identifier.
+             * @param service Metatada about the service that is providing access to
+             * this destination.
+             * @param name The destination's name for display to the user.
+             */
+            public Builder(@NonNull String id, @NonNull ServiceMetadata service,
+                    @NonNull CharSequence name) {
+                if (TextUtils.isEmpty(id)) {
+                    throw new IllegalArgumentException("id must not be null or empty");
+                }
+                if (service == null) {
+                    throw new IllegalArgumentException("service must not be null");
+                }
+                if (TextUtils.isEmpty(name)) {
+                    throw new IllegalArgumentException("name must not be null or empty");
+                }
+                mId = id;
+                mService = service;
+                mName = name;
+            }
+
+            /**
+             * Sets the destination's description for display to the user, or null if none.
+             */
+            public @NonNull Builder setDescription(@Nullable CharSequence description) {
+                mDescription = description;
+                return this;
+            }
+
+            /**
+             * Sets an icon resource from this package used to identify the destination,
+             * or -1 if none.
+             */
+            public @NonNull Builder setIconResourceId(@DrawableRes int resid) {
+                mIconResourceId = resid;
+                return this;
+            }
+
+            /**
+             * Gets optional extra information about the destination, or null if none.
+             */
+            public @NonNull Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Builds the {@link DestinationInfo} object.
+             */
+            public @NonNull DestinationInfo build() {
+                return new DestinationInfo(mId, mService, mName, mDescription,
+                        mIconResourceId, mExtras);
+            }
+        }
+    }
+
+    /**
+     * Describes metadata about a {@link MediaRouteService} which is providing
+     * access to certain kinds of destinations.
+     */
+    public static final class ServiceMetadata {
+        private final ServiceInfo mService;
+        private CharSequence mLabel;
+        private Drawable mIcon;
+
+        ServiceMetadata(Service service) throws NameNotFoundException {
+            mService = service.getPackageManager().getServiceInfo(
+                    new ComponentName(service, service.getClass()),
+                    PackageManager.GET_META_DATA);
+        }
+
+        ServiceMetadata(ServiceInfo service) {
+            mService = service;
+        }
+
+        /**
+         * Gets the service's component information including it name, label and icon.
+         */
+        public @NonNull ServiceInfo getService() {
+            return mService;
+        }
+
+        /**
+         * Gets the service's component name.
+         */
+        public @NonNull ComponentName getComponentName() {
+            return new ComponentName(mService.packageName, mService.name);
+        }
+
+        /**
+         * Gets the service's package name.
+         */
+        public @NonNull String getPackageName() {
+            return mService.packageName;
+        }
+
+        /**
+         * Gets the service's name for display to the user, or null if none.
+         */
+        public @NonNull CharSequence getLabel(@NonNull PackageManager pm) {
+            if (mLabel == null) {
+                mLabel = mService.loadLabel(pm);
+            }
+            return mLabel;
+        }
+
+        /**
+         * Gets the icon drawable, or null if none.
+         */
+        public @Nullable Drawable getIcon(@NonNull PackageManager pm) {
+            if (mIcon == null) {
+                mIcon = mService.loadIcon(pm);
+            }
+            return mIcon;
+        }
+
+        // TODO: add service metadata
+
+        Drawable getDrawable(PackageManager pm, int resid) {
+            return pm.getDrawable(getPackageName(), resid, mService.applicationInfo);
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ServiceInfo{ service=" + getComponentName().toShortString() + " }";
+        }
+    }
+
+    /**
+     * Describes a request to discover routes on behalf of an application.
+     */
+    public static final class DiscoveryRequest {
+        private final ArrayList<MediaRouteSelector> mSelectors =
+                new ArrayList<MediaRouteSelector>();
+        private int mFlags;
+
+        DiscoveryRequest(@NonNull List<MediaRouteSelector> selectors) {
+            setSelectors(selectors);
+        }
+
+        /**
+         * Sets the list of media route selectors to consider during discovery.
+         */
+        public void setSelectors(@NonNull List<MediaRouteSelector> selectors) {
+            if (selectors == null) {
+                throw new IllegalArgumentException("selectors");
+            }
+            mSelectors.clear();
+            mSelectors.addAll(selectors);
+        }
+
+        /**
+         * Gets the list of media route selectors to consider during discovery.
+         */
+        public @NonNull List<MediaRouteSelector> getSelectors() {
+            return mSelectors;
+        }
+
+        /**
+         * Gets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+         */
+        public @DiscoveryFlags int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Sets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+         */
+        public void setFlags(@DiscoveryFlags int flags) {
+            mFlags = flags;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "DiscoveryRequest{ selectors=" + mSelectors
+                    + ", flags=0x" + Integer.toHexString(mFlags)
+                    + " }";
+        }
+    }
+
+    /**
+     * Describes a request to connect to a previously discovered route on
+     * behalf of an application.
+     */
+    public static final class ConnectionRequest {
+        private RouteInfo mRoute;
+        private int mFlags;
+        private Bundle mExtras;
+
+        ConnectionRequest(@NonNull RouteInfo route) {
+            setRoute(route);
+        }
+
+        /**
+         * Gets the route to which to connect.
+         */
+        public @NonNull RouteInfo getRoute() {
+            return mRoute;
+        }
+
+        /**
+         * Sets the route to which to connect.
+         */
+        public void setRoute(@NonNull RouteInfo route) {
+            if (route == null) {
+                throw new IllegalArgumentException("route must not be null");
+            }
+            mRoute = route;
+        }
+
+        /**
+         * Gets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+         */
+        public @ConnectionFlags int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Sets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+         */
+        public void setFlags(@ConnectionFlags int flags) {
+            mFlags = flags;
+        }
+
+        /**
+         * Gets optional extras supplied by the application as part of the call to
+         * connect, or null if none.  The media route service may use this
+         * information to configure the route during connection.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Sets optional extras supplied by the application as part of the call to
+         * connect, or null if none.  The media route service may use this
+         * information to configure the route during connection.
+         */
+        public void setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "ConnectionRequest{ route=" + mRoute
+                    + ", flags=0x" + Integer.toHexString(mFlags)
+                    + ", extras=" + mExtras + " }";
+        }
+    }
+
+    /**
+     * Callback interface to specify policy for route discovery, filtering,
+     * and connection establishment as well as observe media router state changes.
+     */
+    public static abstract class RoutingCallback extends StateCallback {
+        /**
+         * Called to prepare a discovery request object to specify the desired
+         * media route selectors when the media router has been asked to start discovery.
+         * <p>
+         * By default, the discovery request contains all of the selectors which
+         * have been added to the media router.  Subclasses may override the list of
+         * selectors by modifying the discovery request object before returning.
+         * </p>
+         *
+         * @param request The discovery request object which may be modified by
+         * this method to alter how discovery will be performed.
+         * @param selectors The immutable list of media route selectors which were
+         * added to the media router.
+         * @return True to allow discovery to proceed or false to abort it.
+         * By default, this methods returns true.
+         */
+        public boolean onPrepareDiscoveryRequest(@NonNull DiscoveryRequest request,
+                @NonNull List<MediaRouteSelector> selectors) {
+            return true;
+        }
+
+        /**
+         * Called to prepare a connection request object to specify the desired
+         * route and connection parameters when the media router has been asked to
+         * connect to a particular destination.
+         * <p>
+         * By default, the connection request specifies the first available route
+         * to the destination.  Subclasses may override the route and destination
+         * or set additional connection parameters by modifying the connection request
+         * object before returning.
+         * </p>
+         *
+         * @param request The connection request object which may be modified by
+         * this method to alter how the connection will be established.
+         * @param destination The destination to which the media router was asked
+         * to connect.
+         * @param routes The list of routes that belong to that destination sorted
+         * in the same order as their matching media route selectors which were
+         * used during discovery.
+         * @return True to allow the connection to proceed or false to abort it.
+         * By default, this methods returns true.
+         */
+        public boolean onPrepareConnectionRequest(
+                @NonNull ConnectionRequest request,
+                @NonNull DestinationInfo destination, @NonNull List<RouteInfo> routes) {
+            return true;
+        }
+    }
+
+    /**
+     * Callback class to receive events from a {@link MediaRouter.Delegate}.
+     */
+    public static abstract class StateCallback {
+        /**
+         * Called when the media router has been released.
+         */
+        public void onReleased() { }
+
+        /**
+         * Called when the discovery state has changed.
+         *
+         * @param state The new discovery state: one of
+         * {@link #DISCOVERY_STATE_STOPPED} or {@link #DISCOVERY_STATE_STARTED}.
+         */
+        public void onDiscoveryStateChanged(@DiscoveryState int state) { }
+
+        /**
+         * Called when the connection state has changed.
+         *
+         * @param state The new connection state: one of
+         * {@link #CONNECTION_STATE_DISCONNECTED}, {@link #CONNECTION_STATE_CONNECTING}
+         * or {@link #CONNECTION_STATE_CONNECTED}.
+         */
+        public void onConnectionStateChanged(@ConnectionState int state) { }
+
+        /**
+         * Called when the selected destination has changed.
+         *
+         * @param destination The new selected destination, or null if none.
+         */
+        public void onSelectedDestinationChanged(@Nullable DestinationInfo destination) { }
+
+        /**
+         * Called when route discovery has started.
+         */
+        public void onDiscoveryStarted() { }
+
+        /**
+         * Called when route discovery has stopped normally.
+         * <p>
+         * Abnormal termination is reported via {@link #onDiscoveryFailed}.
+         * </p>
+         */
+        public void onDiscoveryStopped() { }
+
+        /**
+         * Called when discovery has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN},
+         * {@link MediaRouter#DISCOVERY_ERROR_ABORTED},
+         * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onDiscoveryFailed(@DiscoveryError int error, @Nullable CharSequence message,
+                @Nullable Bundle extras) { }
+
+        /**
+         * Called when a new destination is found or has changed during discovery.
+         * <p>
+         * Certain destinations may be omitted because they have been filtered
+         * out by the media router's routing callback.
+         * </p>
+         *
+         * @param destination The destination that was found.
+         */
+        public void onDestinationFound(@NonNull DestinationInfo destination) { }
+
+        /**
+         * Called when a destination is no longer reachable or is no longer
+         * offering any routes that satisfy the discovery request.
+         *
+         * @param destination The destination that went away.
+         */
+        public void onDestinationLost(@NonNull DestinationInfo destination) { }
+
+        /**
+         * Called when a connection attempt begins.
+         */
+        public void onConnecting() { }
+
+        /**
+         * Called when the connection succeeds.
+         */
+        public void onConnected() { }
+
+        /**
+         * Called when the connection is terminated normally.
+         * <p>
+         * Abnormal termination is reported via {@link #onConnectionFailed}.
+         * </p>
+         */
+        public void onDisconnected() { }
+
+        /**
+         * Called when a connection attempt or connection in
+         * progress has failed in a non-recoverable manner.
+         *
+         * @param error The error code: one of
+         * {@link MediaRouter#CONNECTION_ERROR_ABORTED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED},
+         * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE},
+         * {@link MediaRouter#CONNECTION_ERROR_BUSY},
+         * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT},
+         * {@link MediaRouter#CONNECTION_ERROR_BROKEN},
+         * or {@link MediaRouter#CONNECTION_ERROR_BARGED}.
+         * @param message The localized error message, or null if none.  This message
+         * may be shown to the user.
+         * @param extras Additional information about the error which a client
+         * may use, or null if none.
+         */
+        public void onConnectionFailed(@ConnectionError int error,
+                @Nullable CharSequence message, @Nullable Bundle extras) { }
+    }
+}
diff --git a/media/java/android/media/session/RouteEvent.aidl b/media/java/android/media/routing/ParcelableConnectionInfo.aidl
similarity index 89%
copy from media/java/android/media/session/RouteEvent.aidl
copy to media/java/android/media/routing/ParcelableConnectionInfo.aidl
index 6966207..4a9ec94 100644
--- a/media/java/android/media/session/RouteEvent.aidl
+++ b/media/java/android/media/routing/ParcelableConnectionInfo.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteEvent;
+parcelable ParcelableConnectionInfo;
diff --git a/media/java/android/media/routing/ParcelableConnectionInfo.java b/media/java/android/media/routing/ParcelableConnectionInfo.java
new file mode 100644
index 0000000..45cfe9f
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableConnectionInfo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.routing;
+
+import android.media.AudioAttributes;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Internal parcelable representation of a media route connection.
+ */
+class ParcelableConnectionInfo implements Parcelable {
+    public AudioAttributes audioAttributes;
+    public int presentationDisplayId = -1;
+    // todo: volume
+    public IBinder[] protocolBinders;
+    public Bundle extras;
+
+    public static final Parcelable.Creator<ParcelableConnectionInfo> CREATOR =
+            new Parcelable.Creator<ParcelableConnectionInfo>() {
+        @Override
+        public ParcelableConnectionInfo createFromParcel(Parcel source) {
+            ParcelableConnectionInfo info = new ParcelableConnectionInfo();
+            if (source.readInt() != 0) {
+                info.audioAttributes = AudioAttributes.CREATOR.createFromParcel(source);
+            }
+            info.presentationDisplayId = source.readInt();
+            info.protocolBinders = source.createBinderArray();
+            info.extras = source.readBundle();
+            return info;
+        }
+
+        @Override
+        public ParcelableConnectionInfo[] newArray(int size) {
+            return new ParcelableConnectionInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (audioAttributes != null) {
+            dest.writeInt(1);
+            audioAttributes.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeInt(presentationDisplayId);
+        dest.writeBinderArray(protocolBinders);
+        dest.writeBundle(extras);
+    }
+}
diff --git a/media/java/android/media/session/RouteEvent.aidl b/media/java/android/media/routing/ParcelableDestinationInfo.aidl
similarity index 89%
copy from media/java/android/media/session/RouteEvent.aidl
copy to media/java/android/media/routing/ParcelableDestinationInfo.aidl
index 6966207..bf1c198 100644
--- a/media/java/android/media/session/RouteEvent.aidl
+++ b/media/java/android/media/routing/ParcelableDestinationInfo.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteEvent;
+parcelable ParcelableDestinationInfo;
diff --git a/media/java/android/media/routing/ParcelableDestinationInfo.java b/media/java/android/media/routing/ParcelableDestinationInfo.java
new file mode 100644
index 0000000..eca5eec
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableDestinationInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.routing;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Internal parcelable representation of a media destination.
+ */
+class ParcelableDestinationInfo implements Parcelable {
+    public String id;
+    public CharSequence name;
+    public CharSequence description;
+    public int iconResourceId;
+    public Bundle extras;
+
+    public static final Parcelable.Creator<ParcelableDestinationInfo> CREATOR =
+            new Parcelable.Creator<ParcelableDestinationInfo>() {
+        @Override
+        public ParcelableDestinationInfo createFromParcel(Parcel source) {
+            ParcelableDestinationInfo info = new ParcelableDestinationInfo();
+            info.id = source.readString();
+            info.name = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            info.description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            info.iconResourceId = source.readInt();
+            info.extras = source.readBundle();
+            return info;
+        }
+
+        @Override
+        public ParcelableDestinationInfo[] newArray(int size) {
+            return new ParcelableDestinationInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        TextUtils.writeToParcel(name, dest, flags);
+        TextUtils.writeToParcel(description, dest, flags);
+        dest.writeInt(iconResourceId);
+        dest.writeBundle(extras);
+    }
+}
diff --git a/media/java/android/media/session/RouteEvent.aidl b/media/java/android/media/routing/ParcelableRouteInfo.aidl
similarity index 90%
copy from media/java/android/media/session/RouteEvent.aidl
copy to media/java/android/media/routing/ParcelableRouteInfo.aidl
index 6966207..126afaa 100644
--- a/media/java/android/media/session/RouteEvent.aidl
+++ b/media/java/android/media/routing/ParcelableRouteInfo.aidl
@@ -13,6 +13,6 @@
 ** limitations under the License.
 */
 
-package android.media.session;
+package android.media.routing;
 
-parcelable RouteEvent;
+parcelable ParcelableRouteInfo;
diff --git a/media/java/android/media/routing/ParcelableRouteInfo.java b/media/java/android/media/routing/ParcelableRouteInfo.java
new file mode 100644
index 0000000..fb1a547
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableRouteInfo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.routing;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Internal parcelable representation of a media route.
+ */
+class ParcelableRouteInfo implements Parcelable {
+    public String id;
+    public int selectorIndex; // index of selector within list used for discovery
+    public int features;
+    public String[] protocols;
+    public Bundle extras;
+
+    public static final Parcelable.Creator<ParcelableRouteInfo> CREATOR =
+            new Parcelable.Creator<ParcelableRouteInfo>() {
+        @Override
+        public ParcelableRouteInfo createFromParcel(Parcel source) {
+            ParcelableRouteInfo info = new ParcelableRouteInfo();
+            info.id = source.readString();
+            info.selectorIndex = source.readInt();
+            info.features = source.readInt();
+            info.protocols = source.createStringArray();
+            info.extras = source.readBundle();
+            return info;
+        }
+
+        @Override
+        public ParcelableRouteInfo[] newArray(int size) {
+            return new ParcelableRouteInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        dest.writeInt(selectorIndex);
+        dest.writeInt(features);
+        dest.writeStringArray(protocols);
+        dest.writeBundle(extras);
+    }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 5bc0de4..a92350b 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -17,10 +17,8 @@
 
 import android.content.ComponentName;
 import android.media.MediaMetadata;
+import android.media.routing.IMediaRouter;
 import android.media.session.ISessionController;
-import android.media.session.RouteOptions;
-import android.media.session.RouteCommand;
-import android.media.session.RouteInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -34,17 +32,10 @@
     ISessionController getController();
     void setFlags(int flags);
     void setActive(boolean active);
+    void setMediaRouter(in IMediaRouter router);
     void setMediaButtonReceiver(in ComponentName mbr);
     void destroy();
 
-    // These commands are for setting up and communicating with routes
-    // Returns true if the route was set for this session
-    boolean setRoute(in RouteInfo route);
-    void setRouteOptions(in List<RouteOptions> options);
-    void connectToRoute(in RouteInfo route, in RouteOptions options);
-    void disconnectFromRoute(in RouteInfo route);
-    void sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
-
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata);
     void setPlaybackState(in PlaybackState state);
@@ -53,4 +44,4 @@
     // These commands relate to volume handling
     void configureVolumeHandling(int type, int arg1, int arg2);
     void setCurrentVolume(int currentVolume);
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 0316d1fa..e554e27 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -16,9 +16,6 @@
 package android.media.session;
 
 import android.media.Rating;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -29,11 +26,6 @@
 oneway interface ISessionCallback {
     void onCommand(String command, in Bundle extras, in ResultReceiver cb);
     void onMediaButton(in Intent mediaButtonIntent, int sequenceNumber, in ResultReceiver cb);
-    void onRequestRouteChange(in RouteInfo route);
-    void onRouteConnected(in RouteInfo route, in RouteOptions options);
-    void onRouteDisconnected(in RouteInfo route, int reason);
-    void onRouteStateChange(int state);
-    void onRouteEvent(in RouteEvent event);
 
     // These callbacks are for the TransportPerformer
     void onPlay();
@@ -49,4 +41,4 @@
     // These callbacks are for volume handling
     void onAdjustVolumeBy(int delta);
     void onSetVolumeTo(int value);
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index b4c11f6..6cf5ef2 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -18,6 +18,8 @@
 import android.content.Intent;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.routing.IMediaRouterDelegate;
+import android.media.routing.IMediaRouterStateCallback;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaSessionInfo;
 import android.media.session.ParcelableVolumeInfo;
@@ -36,14 +38,15 @@
     void registerCallbackListener(in ISessionControllerCallback cb);
     void unregisterCallbackListener(in ISessionControllerCallback cb);
     boolean isTransportControlEnabled();
-    void showRoutePicker();
     MediaSessionInfo getSessionInfo();
     long getFlags();
     ParcelableVolumeInfo getVolumeAttributes();
     void adjustVolumeBy(int delta, int flags);
     void setVolumeTo(int value, int flags);
 
-    // These commands are for the TransportController
+    IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback);
+
+    // These commands are for the TransportControls
     void play();
     void pause();
     void stop();
@@ -56,4 +59,4 @@
     MediaMetadata getMetadata();
     PlaybackState getPlaybackState();
     int getRatingType();
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index baa1379..64d2bc7 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,7 +16,6 @@
 package android.media.session;
 
 import android.media.MediaMetadata;
-import android.media.session.RouteInfo;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
@@ -26,10 +25,9 @@
  */
 oneway interface ISessionControllerCallback {
     void onEvent(String event, in Bundle extras);
-    void onRouteChanged(in RouteInfo route);
 
     // These callbacks are for the TransportController
     void onPlaybackStateChanged(in PlaybackState state);
     void onMetadataChanged(in MediaMetadata metadata);
     void onVolumeInfoChanged(in ParcelableVolumeInfo info);
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index edb69bc..cc8b31a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -22,6 +22,7 @@
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.routing.MediaRouter;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -52,8 +53,7 @@
     private static final int MSG_EVENT = 1;
     private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
     private static final int MSG_UPDATE_METADATA = 3;
-    private static final int MSG_ROUTE = 4;
-    private static final int MSG_UPDATE_VOLUME = 5;
+    private static final int MSG_UPDATE_VOLUME = 4;
 
     private final ISessionController mSessionBinder;
 
@@ -64,11 +64,11 @@
     private boolean mCbRegistered = false;
     private MediaSessionInfo mInfo;
 
-    private TransportControls mTransportController;
+    private final TransportControls mTransportControls;
 
     private MediaController(ISessionController sessionBinder) {
         mSessionBinder = sessionBinder;
-        mTransportController = new TransportControls();
+        mTransportControls = new TransportControls();
     }
 
     /**
@@ -91,12 +91,24 @@
     }
 
     /**
-     * Get a {@link TransportControls} instance for this session.
+     * Get a {@link TransportControls} instance to send transport actions to
+     * the associated session.
      *
-     * @return A controls instance
+     * @return A transport controls instance.
      */
     public @NonNull TransportControls getTransportControls() {
-        return mTransportController;
+        return mTransportControls;
+    }
+
+    /**
+     * Creates a media router delegate through which the destination of the media
+     * router may be observed and controlled.
+     *
+     * @return The media router delegate, or null if the media session does
+     * not support media routing.
+     */
+    public @Nullable MediaRouter.Delegate createMediaRouterDelegate() {
+        return new MediaRouter.Delegate();
     }
 
     /**
@@ -308,20 +320,6 @@
     }
 
     /**
-     * Request that the route picker be shown for this session. This should
-     * generally be called in response to a user action.
-     *
-     * @hide
-     */
-    public void showRoutePicker() {
-        try {
-            mSessionBinder.showRoutePicker();
-        } catch (RemoteException e) {
-            Log.d(TAG, "Dead object in showRoutePicker", e);
-        }
-    }
-
-    /**
      * Get the info for the session this controller is connected to.
      *
      * @return The session info for the connected session.
@@ -421,15 +419,6 @@
         }
 
         /**
-         * Override to handle route changes for this session.
-         *
-         * @param route The new route
-         * @hide
-         */
-        public void onRouteChanged(RouteInfo route) {
-        }
-
-        /**
          * Override to handle changes in playback state.
          *
          * @param state The new playback state of the session
@@ -670,14 +659,6 @@
         }
 
         @Override
-        public void onRouteChanged(RouteInfo route) {
-            MediaController controller = mController.get();
-            if (controller != null) {
-                controller.postMessage(MSG_ROUTE, route, null);
-            }
-        }
-
-        @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             MediaController controller = mController.get();
             if (controller != null) {
@@ -719,9 +700,6 @@
                 case MSG_EVENT:
                     mCallback.onSessionEvent((String) msg.obj, msg.getData());
                     break;
-                case MSG_ROUTE:
-                    mCallback.onRouteChanged((RouteInfo) msg.obj);
-                    break;
                 case MSG_UPDATE_PLAYBACK_STATE:
                     mCallback.onPlaybackStateChanged((PlaybackState) msg.obj);
                     break;
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 2cbdc96..34997bd 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -25,6 +25,7 @@
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.routing.MediaRouter;
 import android.media.session.ISessionController;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -93,40 +94,6 @@
     public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
 
     /**
-     * Indicates the session was disconnected because the user that the session
-     * belonged to is stopping.
-     *
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_USER_STOPPING = 1;
-
-    /**
-     * Indicates the session was disconnected because the provider disconnected
-     * the route.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_PROVIDER_DISCONNECTED = 2;
-
-    /**
-     * Indicates the session was disconnected because the route has changed.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_ROUTE_CHANGED = 3;
-
-    /**
-     * Indicates the session was disconnected because the session owner
-     * requested it disconnect.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_SESSION_DISCONNECTED = 4;
-
-    /**
-     * Indicates the session was disconnected because it was destroyed.
-     * @hide
-     */
-    public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
-
-    /**
      * The session uses local playback.
      */
     public static final int PLAYBACK_TYPE_LOCAL = 1;
@@ -146,11 +113,7 @@
             = new ArrayList<CallbackMessageHandler>();
     private final ArrayList<TransportMessageHandler> mTransportCallbacks
             = new ArrayList<TransportMessageHandler>();
-    // TODO route interfaces
-    private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners
-            = new ArrayMap<String, RouteInterface.EventListener>();
 
-    private Route mRoute;
     private VolumeProvider mVolumeProvider;
 
     private boolean mActive = false;
@@ -228,6 +191,23 @@
     }
 
     /**
+     * Associates a {@link MediaRouter} with this session to control the destination
+     * of media content.
+     * <p>
+     * A media router may only be associated with at most one session at a time.
+     * </p>
+     *
+     * @param router The media router, or null to remove the current association.
+     */
+    public void setMediaRouter(@Nullable MediaRouter router) {
+        try {
+            mBinder.setMediaRouter(router != null ? router.getBinder() : null);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+        }
+    }
+
+    /**
      * Set a media button event receiver component to use to restart playback
      * after an app has been stopped.
      *
@@ -377,90 +357,6 @@
     }
 
     /**
-     * Connect to the current route using the specified request.
-     * <p>
-     * Connection updates will be sent to the callback's
-     * {@link Callback#onRouteConnected(Route)} and
-     * {@link Callback#onRouteDisconnected(Route, int)} methods. If the
-     * connection fails {@link Callback#onRouteDisconnected(Route, int)} will be
-     * called.
-     * <p>
-     * If you already have a connection to this route it will be disconnected
-     * before the new connection is established. TODO add an easy way to compare
-     * MediaRouteOptions.
-     *
-     * @param route The route the app is trying to connect to.
-     * @param request The connection request to use.
-     * @hide
-     */
-    public void connect(RouteInfo route, RouteOptions request) {
-        if (route == null) {
-            throw new IllegalArgumentException("Must specify the route");
-        }
-        if (request == null) {
-            throw new IllegalArgumentException("Must specify the connection request");
-        }
-        try {
-            mBinder.connectToRoute(route, request);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error starting connection to route", e);
-        }
-    }
-
-    /**
-     * Disconnect from the current route. After calling you will be switched
-     * back to the default route.
-     *
-     * @hide
-     */
-    public void disconnect() {
-        if (mRoute != null) {
-            try {
-                mBinder.disconnectFromRoute(mRoute.getRouteInfo());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error disconnecting from route");
-            }
-        }
-    }
-
-    /**
-     * Set the list of route options your app is interested in connecting to. It
-     * will be used for picking valid routes.
-     *
-     * @param options The set of route options your app may use to connect.
-     * @hide
-     */
-    public void setRouteOptions(List<RouteOptions> options) {
-        try {
-            mBinder.setRouteOptions(options);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error setting route options.", e);
-        }
-    }
-
-    /**
-     * @hide
-     * TODO allow multiple listeners for the same interface, allow removal
-     */
-    public void addInterfaceListener(String iface,
-            RouteInterface.EventListener listener) {
-        mInterfaceListeners.put(iface, listener);
-    }
-
-    /**
-     * @hide
-     */
-    public boolean sendRouteCommand(RouteCommand command, ResultReceiver cb) {
-        try {
-            mBinder.sendRouteCommand(command, cb);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error sending command to route.", e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
      * Add a callback to receive transport controls on, such as play, rewind, or
      * fast forward.
      *
@@ -670,34 +566,6 @@
         }
     }
 
-    private void postRequestRouteChange(RouteInfo route) {
-        synchronized (mLock) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_CHANGE, route);
-            }
-        }
-    }
-
-    private void postRouteConnected(RouteInfo route, RouteOptions options) {
-        synchronized (mLock) {
-            mRoute = new Route(route, options, this);
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_CONNECTED, mRoute);
-            }
-        }
-    }
-
-    private void postRouteDisconnected(RouteInfo route, int reason) {
-        synchronized (mLock) {
-            if (mRoute != null && TextUtils.equals(mRoute.getRouteInfo().getId(), route.getId())) {
-                for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                    mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_DISCONNECTED, mRoute,
-                            reason);
-                }
-            }
-        }
-    }
-
     /**
      * Return true if this is considered an active playback state.
      *
@@ -796,47 +664,6 @@
         public void onControlCommand(@NonNull String command, @Nullable Bundle extras,
                 @Nullable ResultReceiver cb) {
         }
-
-        /**
-         * Called when the user has selected a different route to connect to.
-         * The app is responsible for connecting to the new route and migrating
-         * ongoing playback if necessary.
-         *
-         * @param route
-         * @hide
-         */
-        public void onRequestRouteChange(RouteInfo route) {
-        }
-
-        /**
-         * Called when a route has successfully connected. Calls to the route
-         * are now valid.
-         *
-         * @param route The route that was connected
-         * @hide
-         */
-        public void onRouteConnected(Route route) {
-        }
-
-        /**
-         * Called when a route was disconnected. Further calls to the route will
-         * fail. If available a reason for being disconnected will be provided.
-         * <p>
-         * Valid reasons are:
-         * <ul>
-         * <li>{@link #DISCONNECT_REASON_USER_STOPPING}</li>
-         * <li>{@link #DISCONNECT_REASON_PROVIDER_DISCONNECTED}</li>
-         * <li>{@link #DISCONNECT_REASON_ROUTE_CHANGED}</li>
-         * <li>{@link #DISCONNECT_REASON_SESSION_DISCONNECTED}</li>
-         * <li>{@link #DISCONNECT_REASON_SESSION_DESTROYED}</li>
-         * </ul>
-         *
-         * @param route The route that disconnected
-         * @param reason The reason for the disconnect
-         * @hide
-         */
-        public void onRouteDisconnected(Route route, int reason) {
-        }
     }
 
     /**
@@ -902,17 +729,6 @@
          */
         public void onSetRating(@NonNull Rating rating) {
         }
-
-        /**
-         * Report that audio focus has changed on the app. This only happens if
-         * you have indicated you have started playing with
-         * {@link #setPlaybackState}.
-         *
-         * @param focusChange The type of focus change, TBD.
-         * @hide
-         */
-        public void onRouteFocusChange(int focusChange) {
-        }
     }
 
     /**
@@ -926,8 +742,7 @@
         }
 
         @Override
-        public void onCommand(String command, Bundle extras, ResultReceiver cb)
-                throws RemoteException {
+        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.postCommand(command, extras, cb);
@@ -935,8 +750,8 @@
         }
 
         @Override
-        public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb)
-                throws RemoteException {
+        public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,
+                ResultReceiver cb) {
             MediaSession session = mMediaSession.get();
             try {
                 if (session != null) {
@@ -950,31 +765,7 @@
         }
 
         @Override
-        public void onRequestRouteChange(RouteInfo route) throws RemoteException {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.postRequestRouteChange(route);
-            }
-        }
-
-        @Override
-        public void onRouteConnected(RouteInfo route, RouteOptions options) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.postRouteConnected(route, options);
-            }
-        }
-
-        @Override
-        public void onRouteDisconnected(RouteInfo route, int reason) {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                session.postRouteDisconnected(route, reason);
-            }
-        }
-
-        @Override
-        public void onPlay() throws RemoteException {
+        public void onPlay() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPlay();
@@ -982,7 +773,7 @@
         }
 
         @Override
-        public void onPause() throws RemoteException {
+        public void onPause() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPause();
@@ -990,7 +781,7 @@
         }
 
         @Override
-        public void onStop() throws RemoteException {
+        public void onStop() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchStop();
@@ -998,7 +789,7 @@
         }
 
         @Override
-        public void onNext() throws RemoteException {
+        public void onNext() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchNext();
@@ -1006,7 +797,7 @@
         }
 
         @Override
-        public void onPrevious() throws RemoteException {
+        public void onPrevious() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPrevious();
@@ -1014,7 +805,7 @@
         }
 
         @Override
-        public void onFastForward() throws RemoteException {
+        public void onFastForward() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchFastForward();
@@ -1022,7 +813,7 @@
         }
 
         @Override
-        public void onRewind() throws RemoteException {
+        public void onRewind() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchRewind();
@@ -1030,7 +821,7 @@
         }
 
         @Override
-        public void onSeekTo(long pos) throws RemoteException {
+        public void onSeekTo(long pos) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchSeekTo(pos);
@@ -1038,7 +829,7 @@
         }
 
         @Override
-        public void onRate(Rating rating) throws RemoteException {
+        public void onRate(Rating rating) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchRate(rating);
@@ -1046,26 +837,7 @@
         }
 
         @Override
-        public void onRouteEvent(RouteEvent event) throws RemoteException {
-            MediaSession session = mMediaSession.get();
-            if (session != null) {
-                RouteInterface.EventListener iface
-                        = session.mInterfaceListeners.get(event.getIface());
-                Log.d(TAG, "Received route event on iface " + event.getIface() + ". Listener is "
-                        + iface);
-                if (iface != null) {
-                    iface.onEvent(event.getEvent(), event.getExtras());
-                }
-            }
-        }
-
-        @Override
-        public void onRouteStateChange(int state) throws RemoteException {
-            // TODO
-        }
-
-        @Override
-        public void onAdjustVolumeBy(int delta) throws RemoteException {
+        public void onAdjustVolumeBy(int delta) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 if (session.mVolumeProvider != null) {
@@ -1075,7 +847,7 @@
         }
 
         @Override
-        public void onSetVolumeTo(int value) throws RemoteException {
+        public void onSetVolumeTo(int value) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 if (session.mVolumeProvider != null) {
@@ -1089,9 +861,6 @@
     private class CallbackMessageHandler extends Handler {
         private static final int MSG_MEDIA_BUTTON = 1;
         private static final int MSG_COMMAND = 2;
-        private static final int MSG_ROUTE_CHANGE = 3;
-        private static final int MSG_ROUTE_CONNECTED = 4;
-        private static final int MSG_ROUTE_DISCONNECTED = 5;
 
         private MediaSession.Callback mCallback;
 
@@ -1114,15 +883,6 @@
                         Command cmd = (Command) msg.obj;
                         mCallback.onControlCommand(cmd.command, cmd.extras, cmd.stub);
                         break;
-                    case MSG_ROUTE_CHANGE:
-                        mCallback.onRequestRouteChange((RouteInfo) msg.obj);
-                        break;
-                    case MSG_ROUTE_CONNECTED:
-                        mCallback.onRouteConnected((Route) msg.obj);
-                        break;
-                    case MSG_ROUTE_DISCONNECTED:
-                        mCallback.onRouteDisconnected((Route) msg.obj, msg.arg1);
-                        break;
                 }
             }
         }
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 5d1a7e02..11f7720 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -202,7 +202,7 @@
                 if (up) {
                     flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
                 } else {
-                    flags = AudioManager.FLAG_SHOW_UI;
+                    flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
                 }
             }
 
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c73a8d3..c477406 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -35,9 +35,8 @@
 import java.util.List;
 
 /**
- * MediaSessionManager allows the creation and control of MediaSessions in the
- * system. A MediaSession enables publishing information about ongoing media and
- * interacting with MediaControllers and MediaRoutes.
+ * Provides support for interacting with {@link MediaSession media sessions}
+ * that applications have published to express their ongoing media playback state.
  * <p>
  * Use <code>Context.getSystemService(Context.MEDIA_SESSION_SERVICE)</code> to
  * get an instance of this class.
@@ -256,8 +255,8 @@
     }
 
     /**
-     * Dispatch an adjust volume request to the system. It will be routed to the
-     * most relevant stream/session.
+     * Dispatch an adjust volume request to the system. It will be sent to the
+     * most relevant audio stream or media session.
      *
      * @param suggestedStream The stream to fall back to if there isn't a
      *            relevant stream
@@ -292,8 +291,7 @@
 
         private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
             @Override
-            public void onActiveSessionsChanged(List<MediaSession.Token> tokens)
-                    throws RemoteException {
+            public void onActiveSessionsChanged(List<MediaSession.Token> tokens) {
                 ArrayList<MediaController> controllers = new ArrayList<MediaController>();
                 int size = tokens.size();
                 for (int i = 0; i < size; i++) {
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 6125cb4..f7e7176 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -157,10 +157,11 @@
 
     /**
      * State indicating the class doing playback is currently connecting to a
-     * route. Depending on the implementation you may return to the previous
-     * state when the connection finishes or enter {@link #STATE_NONE}. If
-     * the connection failed {@link #STATE_ERROR} should be used.
-     * @hide
+     * new destination.  Depending on the implementation you may return to the previous
+     * state when the connection finishes or enter {@link #STATE_NONE}.
+     * If the connection failed {@link #STATE_ERROR} should be used.
+     *
+     * @see #setState
      */
     public final static int STATE_CONNECTING = 8;
 
@@ -183,41 +184,29 @@
      */
     public final static long PLAYBACK_POSITION_UNKNOWN = -1;
 
-    private int mState;
-    private long mPosition;
-    private long mBufferPosition;
-    private float mRate;
-    private long mActions;
-    private CharSequence mErrorMessage;
-    private long mUpdateTime;
+    private final int mState;
+    private final long mPosition;
+    private final long mBufferPosition;
+    private final float mSpeed;
+    private final long mActions;
+    private final CharSequence mErrorMessage;
+    private final long mUpdateTime;
 
-    /**
-     * Create an empty PlaybackState. At minimum a state and actions should be
-     * set before publishing a PlaybackState.
-     */
-    public PlaybackState() {
-    }
-
-    /**
-     * Create a new PlaybackState from an existing PlaybackState. All fields
-     * will be copied to the new state.
-     *
-     * @param from The PlaybackState to duplicate
-     */
-    public PlaybackState(PlaybackState from) {
-        mState = from.mState;
-        mPosition = from.mPosition;
-        mRate = from.mRate;
-        mUpdateTime = from.mUpdateTime;
-        mBufferPosition = from.mBufferPosition;
-        mActions = from.mActions;
-        mErrorMessage = from.mErrorMessage;
+    private PlaybackState(int state, long position, long updateTime, float speed,
+            long bufferPosition, long actions, CharSequence error) {
+        mState = state;
+        mPosition = position;
+        mSpeed = speed;
+        mUpdateTime = updateTime;
+        mBufferPosition = bufferPosition;
+        mActions = actions;
+        mErrorMessage = error;
     }
 
     private PlaybackState(Parcel in) {
         mState = in.readInt();
         mPosition = in.readLong();
-        mRate = in.readFloat();
+        mSpeed = in.readFloat();
         mUpdateTime = in.readLong();
         mBufferPosition = in.readLong();
         mActions = in.readLong();
@@ -231,7 +220,7 @@
         bob.append("state=").append(mState);
         bob.append(", position=").append(mPosition);
         bob.append(", buffered position=").append(mBufferPosition);
-        bob.append(", rate=").append(mRate);
+        bob.append(", speed=").append(mSpeed);
         bob.append(", updated=").append(mUpdateTime);
         bob.append(", actions=").append(mActions);
         bob.append(", error=").append(mErrorMessage);
@@ -248,7 +237,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mState);
         dest.writeLong(mPosition);
-        dest.writeFloat(mRate);
+        dest.writeFloat(mSpeed);
         dest.writeLong(mUpdateTime);
         dest.writeLong(mBufferPosition);
         dest.writeLong(mActions);
@@ -270,41 +259,6 @@
     public int getState() {
         return mState;
     }
-
-    /**
-     * Set the current state of playback.
-     * <p>
-     * The position must be in ms and indicates the current playback position
-     * within the track. If the position is unknown use
-     * {@link #PLAYBACK_POSITION_UNKNOWN}.
-     * <p>
-     * The rate is a multiple of normal playback and should be 0 when paused and
-     * negative when rewinding. Normal playback rate is 1.0.
-     * <p>
-     * The state must be one of the following:
-     * <ul>
-     * <li> {@link PlaybackState#STATE_NONE}</li>
-     * <li> {@link PlaybackState#STATE_STOPPED}</li>
-     * <li> {@link PlaybackState#STATE_PLAYING}</li>
-     * <li> {@link PlaybackState#STATE_PAUSED}</li>
-     * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
-     * <li> {@link PlaybackState#STATE_REWINDING}</li>
-     * <li> {@link PlaybackState#STATE_BUFFERING}</li>
-     * <li> {@link PlaybackState#STATE_ERROR}</li>
-     * </ul>
-     *
-     * @param state The current state of playback.
-     * @param position The position in the current track in ms.
-     * @param playbackRate The current rate of playback as a multiple of normal
-     *            playback.
-     */
-    public void setState(int state, long position, float playbackRate) {
-        this.mState = state;
-        this.mPosition = position;
-        this.mRate = playbackRate;
-        mUpdateTime = SystemClock.elapsedRealtime();
-    }
-
     /**
      * Get the current playback position in ms.
      */
@@ -322,23 +276,14 @@
     }
 
     /**
-     * Set the current buffer position in ms. This is the farthest playback
-     * point that can be reached from the current position using only buffered
-     * content.
-     */
-    public void setBufferPosition(long bufferPosition) {
-        mBufferPosition = bufferPosition;
-    }
-
-    /**
-     * Get the current playback rate as a multiple of normal playback. This
+     * Get the current playback speed as a multiple of normal playback. This
      * should be negative when rewinding. A value of 1 means normal playback and
      * 0 means paused.
      *
-     * @return The current rate of playback.
+     * @return The current speed of playback.
      */
-    public float getPlaybackRate() {
-        return mRate;
+    public float getPlaybackSpeed() {
+        return mSpeed;
     }
 
     /**
@@ -361,25 +306,6 @@
     }
 
     /**
-     * Set the current capabilities available on this session. This should use a
-     * bitmask of the available capabilities.
-     * <ul>
-     * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li>
-     * <li> {@link PlaybackState#ACTION_REWIND}</li>
-     * <li> {@link PlaybackState#ACTION_PLAY}</li>
-     * <li> {@link PlaybackState#ACTION_PAUSE}</li>
-     * <li> {@link PlaybackState#ACTION_STOP}</li>
-     * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li>
-     * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li>
-     * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
-     * <li> {@link PlaybackState#ACTION_SET_RATING}</li>
-     * </ul>
-     */
-    public void setActions(long capabilities) {
-        mActions = capabilities;
-    }
-
-    /**
      * Get a user readable error message. This should be set when the state is
      * {@link PlaybackState#STATE_ERROR}.
      */
@@ -392,21 +318,12 @@
      * position has never been set this will return 0;
      *
      * @return The last time the position was updated.
-     * @hide
      */
     public long getLastPositionUpdateTime() {
         return mUpdateTime;
     }
 
     /**
-     * Set a user readable error message. This should be set when the state is
-     * {@link PlaybackState#STATE_ERROR}.
-     */
-    public void setErrorMessage(CharSequence errorMessage) {
-        mErrorMessage = errorMessage;
-    }
-
-    /**
      * Get the {@link PlaybackState} state for the given
      * {@link RemoteControlClient} state.
      *
@@ -573,4 +490,175 @@
             return new PlaybackState[size];
         }
     };
+
+    /**
+     * Builder for {@link PlaybackState} objects.
+     */
+    public static final class Builder {
+        private int mState;
+        private long mPosition;
+        private long mBufferPosition;
+        private float mSpeed;
+        private long mActions;
+        private CharSequence mErrorMessage;
+        private long mUpdateTime;
+
+        /**
+         * Creates an initially empty state builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Creates a builder with the same initial values as those in the from
+         * state.
+         *
+         * @param from The state to use for initializing the builder.
+         */
+        public Builder(PlaybackState from) {
+            if (from == null) {
+                return;
+            }
+            mState = from.mState;
+            mPosition = from.mPosition;
+            mBufferPosition = from.mBufferPosition;
+            mSpeed = from.mSpeed;
+            mActions = from.mActions;
+            mErrorMessage = from.mErrorMessage;
+            mUpdateTime = from.mUpdateTime;
+        }
+
+        /**
+         * Set the current state of playback.
+         * <p>
+         * The position must be in ms and indicates the current playback
+         * position within the track. If the position is unknown use
+         * {@link #PLAYBACK_POSITION_UNKNOWN}. When not using an unknown
+         * position the time at which the position was updated must be provided.
+         * It is okay to use {@link SystemClock#elapsedRealtime()} if the
+         * current position was just retrieved.
+         * <p>
+         * The speed is a multiple of normal playback and should be 0 when
+         * paused and negative when rewinding. Normal playback speed is 1.0.
+         * <p>
+         * The state must be one of the following:
+         * <ul>
+         * <li> {@link PlaybackState#STATE_NONE}</li>
+         * <li> {@link PlaybackState#STATE_STOPPED}</li>
+         * <li> {@link PlaybackState#STATE_PLAYING}</li>
+         * <li> {@link PlaybackState#STATE_PAUSED}</li>
+         * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
+         * <li> {@link PlaybackState#STATE_REWINDING}</li>
+         * <li> {@link PlaybackState#STATE_BUFFERING}</li>
+         * <li> {@link PlaybackState#STATE_ERROR}</li>
+         * </ul>
+         *
+         * @param state The current state of playback.
+         * @param position The position in the current track in ms.
+         * @param playbackSpeed The current speed of playback as a multiple of
+         *            normal playback.
+         * @param updateTime The time in the {@link SystemClock#elapsedRealtime}
+         *            timebase that the position was updated at.
+         * @return this
+         */
+        public Builder setState(int state, long position, float playbackSpeed, long updateTime) {
+            mState = state;
+            mPosition = position;
+            mUpdateTime = updateTime;
+            mSpeed = playbackSpeed;
+            return this;
+        }
+
+        /**
+         * Set the current state of playback.
+         * <p>
+         * The position must be in ms and indicates the current playback
+         * position within the track. If the position is unknown use
+         * {@link #PLAYBACK_POSITION_UNKNOWN}. The update time will be set to
+         * the current {@link SystemClock#elapsedRealtime()}.
+         * <p>
+         * The speed is a multiple of normal playback and should be 0 when
+         * paused and negative when rewinding. Normal playback speed is 1.0.
+         * <p>
+         * The state must be one of the following:
+         * <ul>
+         * <li> {@link PlaybackState#STATE_NONE}</li>
+         * <li> {@link PlaybackState#STATE_STOPPED}</li>
+         * <li> {@link PlaybackState#STATE_PLAYING}</li>
+         * <li> {@link PlaybackState#STATE_PAUSED}</li>
+         * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
+         * <li> {@link PlaybackState#STATE_REWINDING}</li>
+         * <li> {@link PlaybackState#STATE_BUFFERING}</li>
+         * <li> {@link PlaybackState#STATE_ERROR}</li>
+         * </ul>
+         *
+         * @param state The current state of playback.
+         * @param position The position in the current track in ms.
+         * @param playbackSpeed The current speed of playback as a multiple of
+         *            normal playback.
+         * @return this
+         */
+        public Builder setState(int state, long position, float playbackSpeed) {
+            return setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());
+        }
+
+        /**
+         * Set the current actions available on this session. This should use a
+         * bitmask of possible actions.
+         * <ul>
+         * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li>
+         * <li> {@link PlaybackState#ACTION_REWIND}</li>
+         * <li> {@link PlaybackState#ACTION_PLAY}</li>
+         * <li> {@link PlaybackState#ACTION_PAUSE}</li>
+         * <li> {@link PlaybackState#ACTION_STOP}</li>
+         * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li>
+         * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li>
+         * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
+         * <li> {@link PlaybackState#ACTION_SET_RATING}</li>
+         * </ul>
+         *
+         * @param actions The set of actions allowed.
+         * @return this
+         */
+        public Builder setActions(long actions) {
+            mActions = actions;
+            return this;
+        }
+
+        /**
+         * Set the current buffer position in ms. This is the farthest playback
+         * point that can be reached from the current position using only
+         * buffered content.
+         *
+         * @param bufferPosition The position in ms that playback is buffered
+         *            to.
+         * @return this
+         */
+        public Builder setBufferPosition(long bufferPosition) {
+            mBufferPosition = bufferPosition;
+            return this;
+        }
+
+        /**
+         * Set a user readable error message. This should be set when the state
+         * is {@link PlaybackState#STATE_ERROR}.
+         *
+         * @param error The error message for display to the user.
+         * @return this
+         */
+        public Builder setErrorMessage(CharSequence error) {
+            mErrorMessage = error;
+            return this;
+        }
+
+        /**
+         * Build and return the PlaybackState instance with these values.
+         *
+         * @return A new state instance.
+         */
+        public PlaybackState build() {
+            return new PlaybackState(mState, mPosition, mUpdateTime, mSpeed, mBufferPosition,
+                    mActions, mErrorMessage);
+        }
+    }
 }
diff --git a/media/java/android/media/session/Route.java b/media/java/android/media/session/Route.java
deleted file mode 100644
index 935eb5b..0000000
--- a/media/java/android/media/session/Route.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.session;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Represents a destination which an application has connected to and may send
- * media content.
- * <p>
- * This allows a session owner to interact with a route it has been connected
- * to. The MediaRoute must be used to get {@link RouteInterface}
- * instances which can be used to communicate over a specific interface on the
- * route.
- * @hide
- */
-public final class Route {
-    private static final String TAG = "Route";
-    private final RouteInfo mInfo;
-    private final MediaSession mSession;
-    private final RouteOptions mOptions;
-
-    /**
-     * @hide
-     */
-    public Route(RouteInfo info, RouteOptions options, MediaSession session) {
-        if (info == null || options == null) {
-            throw new IllegalStateException("Route info was not valid!");
-        }
-        mInfo = info;
-        mOptions = options;
-        mSession = session;
-    }
-
-    /**
-     * Get the {@link RouteInfo} for this route.
-     *
-     * @return The info for this route.
-     */
-    public RouteInfo getRouteInfo() {
-        return mInfo;
-    }
-
-    /**
-     * Get the {@link RouteOptions} that were used to connect this route.
-     *
-     * @return The options used to connect to this route.
-     */
-    public RouteOptions getOptions() {
-        return mOptions;
-    }
-
-    /**
-     * Gets an interface provided by this route. If the interface is not
-     * supported by the route, returns null.
-     *
-     * @see RouteInterface
-     * @param iface The name of the interface to create
-     * @return A {@link RouteInterface} or null if the interface is
-     *         not supported.
-     */
-    public RouteInterface getInterface(String iface) {
-        if (TextUtils.isEmpty(iface)) {
-            throw new IllegalArgumentException("iface may not be empty.");
-        }
-        List<String> ifaces = mOptions.getInterfaceNames();
-        if (ifaces != null) {
-            for (int i = ifaces.size() - 1; i >= 0; i--) {
-                if (iface.equals(ifaces.get(i))) {
-                    return new RouteInterface(this, iface, mSession);
-                }
-            }
-        }
-        Log.e(TAG, "Interface not supported by route");
-        return null;
-    }
-
-    /**
-     * @hide
-     */
-    MediaSession getSession() {
-        return mSession;
-    }
-}
diff --git a/media/java/android/media/session/RouteCommand.aidl b/media/java/android/media/session/RouteCommand.aidl
deleted file mode 100644
index 725b308..0000000
--- a/media/java/android/media/session/RouteCommand.aidl
+++ /dev/null
@@ -1,18 +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.media.session;
-
-parcelable RouteCommand;
diff --git a/media/java/android/media/session/RouteCommand.java b/media/java/android/media/session/RouteCommand.java
deleted file mode 100644
index 358bc0a..0000000
--- a/media/java/android/media/session/RouteCommand.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.session;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Represents a command that an application may send to a route.
- * <p>
- * Commands are associated with a specific route and interface supported by that
- * route and sent through the session. This class isn't used directly by apps.
- *
- * @hide
- */
-public final class RouteCommand implements Parcelable {
-    private final String mRoute;
-    private final String mIface;
-    private final String mEvent;
-    private final Bundle mExtras;
-
-    /**
-     * @param route The id of the route this event is being sent on
-     * @param iface The interface the sender used
-     * @param event The event or command
-     * @param extras Any extras included with the event
-     */
-    public RouteCommand(String route, String iface, String event, Bundle extras) {
-        mRoute = route;
-        mIface = iface;
-        mEvent = event;
-        mExtras = extras;
-    }
-
-    private RouteCommand(Parcel in) {
-        mRoute = in.readString();
-        mIface = in.readString();
-        mEvent = in.readString();
-        mExtras = in.readBundle();
-    }
-
-    /**
-     * Get the id for the route this event was sent on.
-     *
-     * @return The route id this event is using
-     */
-    public String getRouteInfo() {
-        return mRoute;
-    }
-
-    /**
-     * Get the interface this event was sent from
-     *
-     * @return The interface for this event
-     */
-    public String getIface() {
-        return mIface;
-    }
-
-    /**
-     * Get the action/name of the event.
-     *
-     * @return The name of event/command.
-     */
-    public String getEvent() {
-        return mEvent;
-    }
-
-    /**
-     * Get any extras included with the event.
-     *
-     * @return The bundle included with the event or null
-     */
-    public Bundle getExtras() {
-        return mExtras;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mRoute);
-        dest.writeString(mIface);
-        dest.writeString(mEvent);
-        dest.writeBundle(mExtras);
-    }
-
-    public static final Parcelable.Creator<RouteCommand> CREATOR
-            = new Parcelable.Creator<RouteCommand>() {
-        @Override
-        public RouteCommand createFromParcel(Parcel in) {
-            return new RouteCommand(in);
-        }
-
-        @Override
-        public RouteCommand[] newArray(int size) {
-            return new RouteCommand[size];
-        }
-    };
-}
diff --git a/media/java/android/media/session/RouteEvent.java b/media/java/android/media/session/RouteEvent.java
deleted file mode 100644
index 918e410..0000000
--- a/media/java/android/media/session/RouteEvent.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.session;
-
-import android.media.routeprovider.RouteConnection;
-import android.media.routeprovider.RouteProviderService;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Represents an event that a route provider is sending to a particular
- * {@link RouteConnection}. Events are associated with a specific interface
- * supported by the connection and sent through the {@link RouteProviderService}.
- * This class isn't used directly by apps.
- *
- * @hide
- */
-public class RouteEvent implements Parcelable {
-    private final IBinder mConnection;
-    private final String mIface;
-    private final String mEvent;
-    private final Bundle mExtras;
-
-    /**
-     * @param connection The connection that this event is for
-     * @param iface The interface the sender used
-     * @param event The event or command
-     * @param extras Any extras included with the event
-     */
-    public RouteEvent(IBinder connection, String iface, String event, Bundle extras) {
-        mConnection = connection;
-        mIface = iface;
-        mEvent = event;
-        mExtras = extras;
-    }
-
-    private RouteEvent(Parcel in) {
-        mConnection = in.readStrongBinder();
-        mIface = in.readString();
-        mEvent = in.readString();
-        mExtras = in.readBundle();
-    }
-
-    /**
-     * Get the connection this event was sent on.
-     *
-     * @return The connection this event is using
-     */
-    public IBinder getConnection() {
-        return mConnection;
-    }
-
-    /**
-     * Get the interface this event was sent from
-     *
-     * @return The interface for this event
-     */
-    public String getIface() {
-        return mIface;
-    }
-
-    /**
-     * Get the action/name of the event.
-     *
-     * @return The name of event/command.
-     */
-    public String getEvent() {
-        return mEvent;
-    }
-
-    /**
-     * Get any extras included with the event.
-     *
-     * @return The bundle included with the event or null
-     */
-    public Bundle getExtras() {
-        return mExtras;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStrongBinder(mConnection);
-        dest.writeString(mIface);
-        dest.writeString(mEvent);
-        dest.writeBundle(mExtras);
-    }
-
-    public static final Parcelable.Creator<RouteEvent> CREATOR
-            = new Parcelable.Creator<RouteEvent>() {
-        @Override
-        public RouteEvent createFromParcel(Parcel in) {
-            return new RouteEvent(in);
-        }
-
-        @Override
-        public RouteEvent[] newArray(int size) {
-            return new RouteEvent[size];
-        }
-    };
-}
diff --git a/media/java/android/media/session/RouteInfo.aidl b/media/java/android/media/session/RouteInfo.aidl
deleted file mode 100644
index c5f50c8..0000000
--- a/media/java/android/media/session/RouteInfo.aidl
+++ /dev/null
@@ -1,18 +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.media.session;
-
-parcelable RouteInfo;
diff --git a/media/java/android/media/session/RouteInfo.java b/media/java/android/media/session/RouteInfo.java
deleted file mode 100644
index 02f78f9..0000000
--- a/media/java/android/media/session/RouteInfo.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.session;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Information about a route, including its display name, a way to identify it,
- * and the ways it can be connected to.
- * @hide
- */
-public final class RouteInfo implements Parcelable {
-    private final String mName;
-    private final String mId;
-    private final String mProviderId;
-    private final List<RouteOptions> mOptions;
-
-    private RouteInfo(String id, String name, String providerId,
-            List<RouteOptions> connRequests) {
-        mId = id;
-        mName = name;
-        mProviderId = providerId;
-        mOptions = connRequests;
-    }
-
-    private RouteInfo(Parcel in) {
-        mId = in.readString();
-        mName = in.readString();
-        mProviderId = in.readString();
-        mOptions = new ArrayList<RouteOptions>();
-        in.readTypedList(mOptions, RouteOptions.CREATOR);
-    }
-
-    /**
-     * Get the displayable name of this route.
-     *
-     * @return A short, user readable name for this route
-     */
-    public String getName() {
-        return mName;
-    }
-
-    /**
-     * Get the unique id for this route.
-     *
-     * @return A unique route id.
-     */
-    public String getId() {
-        return mId;
-    }
-
-    /**
-     * Get the package name of this route's provider.
-     *
-     * @return The package name of this route's provider.
-     */
-    public String getProvider() {
-        return mProviderId;
-    }
-
-    /**
-     * Get the set of connections that may be used with this route.
-     *
-     * @return An array of connection requests that may be used to connect
-     */
-    public List<RouteOptions> getConnectionMethods() {
-        return mOptions;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mId);
-        dest.writeString(mName);
-        dest.writeString(mProviderId);
-        dest.writeTypedList(mOptions);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder bob = new StringBuilder();
-        bob.append("RouteInfo: id=").append(mId).append(", name=").append(mName)
-                .append(", provider=").append(mProviderId).append(", options={");
-        for (int i = 0; i < mOptions.size(); i++) {
-            if (i != 0) {
-                bob.append(", ");
-            }
-            bob.append(mOptions.get(i).toString());
-        }
-        bob.append("}");
-        return bob.toString();
-    }
-
-    public static final Parcelable.Creator<RouteInfo> CREATOR
-            = new Parcelable.Creator<RouteInfo>() {
-        @Override
-        public RouteInfo createFromParcel(Parcel in) {
-            return new RouteInfo(in);
-        }
-
-        @Override
-        public RouteInfo[] newArray(int size) {
-            return new RouteInfo[size];
-        }
-    };
-
-    /**
-     * Helper for creating MediaRouteInfos. A route must have a name and an id.
-     * While options are not strictly required the route cannot be connected to
-     * without at least one set of options.
-     */
-    public static final class Builder {
-        private String mName;
-        private String mId;
-        private String mProviderPackage;
-        private ArrayList<RouteOptions> mOptions;
-
-        /**
-         * Copies an existing route info object. TODO Remove once we have
-         * helpers for creating route infos.
-         *
-         * @param from The existing info to copy.
-         */
-        public Builder(RouteInfo from) {
-            mOptions = new ArrayList<RouteOptions>(from.getConnectionMethods());
-            mName = from.mName;
-            mId = from.mId;
-            mProviderPackage = from.mProviderId;
-        }
-
-        public Builder() {
-            mOptions = new ArrayList<RouteOptions>();
-        }
-
-        /**
-         * Set the user visible name for this route.
-         *
-         * @param name The name of the route
-         * @return The builder for easy chaining.
-         */
-        public Builder setName(String name) {
-            mName = name;
-            return this;
-        }
-
-        /**
-         * Set the id of the route. This should be unique to the provider.
-         *
-         * @param id The unique id of the route.
-         * @return The builder for easy chaining.
-         */
-        public Builder setId(String id) {
-            mId = id;
-            return this;
-        }
-
-        /**
-         * @hide
-         */
-        public Builder setProviderId(String packageName) {
-            mProviderPackage = packageName;
-            return this;
-        }
-
-        /**
-         * Add a set of {@link RouteOptions} to the route. Multiple options
-         * may be added to the same route.
-         *
-         * @param options The options to add to this route.
-         * @return The builder for easy chaining.
-         */
-        public Builder addRouteOptions(RouteOptions options) {
-            mOptions.add(options);
-            return this;
-        }
-
-        /**
-         * Clear the set of {@link RouteOptions} on the route.
-         *
-         * @return The builder for easy chaining
-         */
-        public Builder clearRouteOptions() {
-            mOptions.clear();
-            return this;
-        }
-
-        /**
-         * Build a new MediaRouteInfo.
-         *
-         * @return A new MediaRouteInfo with the values that were set.
-         */
-        public RouteInfo build() {
-            if (TextUtils.isEmpty(mName)) {
-                throw new IllegalArgumentException("Must set a name before building");
-            }
-            if (TextUtils.isEmpty(mId)) {
-                throw new IllegalArgumentException("Must set an id before building");
-            }
-            return new RouteInfo(mId, mName, mProviderPackage, mOptions);
-        }
-
-        /**
-         * Get the current number of options that have been added to this
-         * builder.
-         *
-         * @return The number of options that have been added.
-         */
-        public int getOptionsSize() {
-            return mOptions.size();
-        }
-    }
-}
diff --git a/media/java/android/media/session/RouteInterface.java b/media/java/android/media/session/RouteInterface.java
deleted file mode 100644
index 8de4d89..0000000
--- a/media/java/android/media/session/RouteInterface.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.session;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * A route can support multiple interfaces for a {@link MediaSession} to
- * interact with. To use a specific interface with a route a
- * MediaSessionRouteInterface needs to be retrieved from the route. An
- * implementation of the specific interface, like
- * {@link RoutePlaybackControls}, should be used to simplify communication
- * and reduce errors on that interface.
- *
- * @see RoutePlaybackControls for an example
- * @hide
- */
-public final class RouteInterface {
-    private static final String TAG = "RouteInterface";
-
-    /**
-     * Error indicating the route is currently not connected.
-     */
-    public static final int RESULT_NOT_CONNECTED = -5;
-    /**
-     * Error indicating the session is no longer using the route this command
-     * was sent to.
-     */
-    public static final int RESULT_ROUTE_IS_STALE = -4;
-    /**
-     * Error indicating that the interface does not support the command.
-     */
-    public static final int RESULT_COMMAND_NOT_SUPPORTED = -3;
-    /**
-     * Error indicating that the route does not support the interface.
-     */
-    public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2;
-    /**
-     * Generic error. Extra information about the error may be included in the
-     * result bundle.
-     */
-    public static final int RESULT_ERROR = -1;
-    /**
-     * The command was successful. Extra information may be included in the
-     * result bundle.
-     */
-    public static final int RESULT_SUCCESS = 1;
-
-    private final Route mRoute;
-    private final String mIface;
-    private final MediaSession mSession;
-
-    private final Object mLock = new Object();
-    private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>();
-
-    /**
-     * @hide
-     */
-    RouteInterface(Route route, String iface, MediaSession session) {
-        mRoute = route;
-        mIface = iface;
-        mSession = session;
-        mSession.addInterfaceListener(iface, mEventListener);
-    }
-
-    /**
-     * Send a command using this interface.
-     *
-     * @param command The command to send.
-     * @param extras Any extras to include with the command.
-     * @param cb The callback to receive the result on.
-     * @return true if the command was sent, false otherwise.
-     */
-    public boolean sendCommand(String command, Bundle extras, ResultReceiver cb) {
-        RouteCommand cmd = new RouteCommand(mRoute.getRouteInfo().getId(), mIface,
-                command, extras);
-        return mSession.sendRouteCommand(cmd, cb);
-    }
-
-    /**
-     * Add a listener to this interface. Events will be sent on the caller's
-     * thread.
-     *
-     * @param listener The listener to receive events on.
-     */
-    public void addListener(EventListener listener) {
-        addListener(listener, null);
-    }
-
-    /**
-     * Add a listener for this interface. If a handler is specified events will
-     * be performed on the handler's thread, otherwise the caller's thread will
-     * be used.
-     *
-     * @param listener The listener to receive events on
-     * @param handler The handler whose thread to post calls on
-     */
-    public void addListener(EventListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener may not be null");
-        }
-        if (handler == null) {
-            handler = new Handler();
-        }
-        synchronized (mLock) {
-            if (findIndexOfListenerLocked(listener) != -1) {
-                Log.d(TAG, "Listener is already added, ignoring");
-                return;
-            }
-            mListeners.add(new EventHandler(handler.getLooper(), listener));
-        }
-    }
-
-    /**
-     * Remove a listener from this interface.
-     *
-     * @param listener The listener to stop receiving events on.
-     */
-    public void removeListener(EventListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener may not be null");
-        }
-        synchronized (mLock) {
-            int index = findIndexOfListenerLocked(listener);
-            if (index != -1) {
-                mListeners.remove(index);
-            }
-        }
-    }
-
-    private int findIndexOfListenerLocked(EventListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            EventHandler handler = mListeners.get(i);
-            if (listener == handler.mListener) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private EventListener mEventListener = new EventListener() {
-            @Override
-        public void onEvent(String event, Bundle args) {
-            synchronized (mLock) {
-                for (int i = mListeners.size() - 1; i >= 0; i--) {
-                    mListeners.get(i).postEvent(event, args);
-                }
-            }
-        }
-
-    };
-
-    /**
-     * An EventListener can be registered by an app with TODO to handle events
-     * sent by the session on a specific interface.
-     */
-    public static abstract class EventListener {
-        /**
-         * This is called when an event is received from the interface. Events
-         * are sent by the session owner and will be delivered to all
-         * controllers that are listening to the interface.
-         *
-         * @param event The event that occurred.
-         * @param args Any extras that were included with the event. May be
-         *            null.
-         */
-        public abstract void onEvent(String event, Bundle args);
-    }
-
-    private static final class EventHandler extends Handler {
-
-        private final EventListener mListener;
-
-        public EventHandler(Looper looper, EventListener cb) {
-            super(looper, null, true);
-            mListener = cb;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            mListener.onEvent((String) msg.obj, msg.getData());
-        }
-
-        public void postEvent(String event, Bundle args) {
-            Message msg = obtainMessage(0, event);
-            msg.setData(args);
-            msg.sendToTarget();
-        }
-    }
-}
diff --git a/media/java/android/media/session/RouteOptions.aidl b/media/java/android/media/session/RouteOptions.aidl
deleted file mode 100644
index feaf517..0000000
--- a/media/java/android/media/session/RouteOptions.aidl
+++ /dev/null
@@ -1,18 +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.media.session;
-
-parcelable RouteOptions;
diff --git a/media/java/android/media/session/RouteOptions.java b/media/java/android/media/session/RouteOptions.java
deleted file mode 100644
index b4fb341..0000000
--- a/media/java/android/media/session/RouteOptions.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.session;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Specifies options that an application might use when connecting to a route.
- * This includes things like interfaces, connection parameters, and required
- * features.
- * <p>
- * An application may create several different route options that describe
- * alternative sets of capabilities that it can use and choose the most
- * appropriate route options when it is ready to connect to the route. Each
- * route options instance must specify a complete set of capabilities to request
- * when the connection is established.
- * @hide
- */
-public final class RouteOptions implements Parcelable {
-    private static final String TAG = "RouteOptions";
-
-    private final ArrayList<String> mIfaces;
-    private final Bundle mConnectionParams;
-
-    private RouteOptions(List<String> ifaces, Bundle params) {
-        mIfaces = new ArrayList<String>(ifaces);
-        mConnectionParams = params;
-    }
-
-    private RouteOptions(Parcel in) {
-        mIfaces = new ArrayList<String>();
-        in.readStringList(mIfaces);
-        mConnectionParams = in.readBundle();
-    }
-
-    /**
-     * Get the interfaces this connection wants to use.
-     *
-     * @return The interfaces for this connection
-     */
-    public List<String> getInterfaceNames() {
-        return mIfaces;
-    }
-
-    /**
-     * Get the parameters that will be used for connecting.
-     *
-     * @return The set of connection parameters this connections uses
-     */
-    public Bundle getConnectionParams() {
-        return mConnectionParams;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStringList(mIfaces);
-        dest.writeBundle(mConnectionParams);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder bob = new StringBuilder();
-        bob.append("Options: interfaces={");
-        for (int i = 0; i < mIfaces.size(); i++) {
-            if (i != 0) {
-                bob.append(", ");
-            }
-            bob.append(mIfaces.get(i));
-        }
-        bob.append("}");
-        bob.append(", parameters=");
-        bob.append(mConnectionParams == null ? "null" : mConnectionParams.toString());
-        return bob.toString();
-    }
-
-    public static final Parcelable.Creator<RouteOptions> CREATOR
-            = new Parcelable.Creator<RouteOptions>() {
-        @Override
-        public RouteOptions createFromParcel(Parcel in) {
-            return new RouteOptions(in);
-        }
-
-        @Override
-        public RouteOptions[] newArray(int size) {
-            return new RouteOptions[size];
-        }
-    };
-
-    /**
-     * Builder for creating {@link RouteOptions}.
-     */
-    public final static class Builder {
-        private ArrayList<String> mIfaces = new ArrayList<String>();
-        private Bundle mConnectionParams;
-
-        public Builder() {
-        }
-
-        /**
-         * Add a required interface to the options.
-         *
-         * @param interfaceName The name of the interface to add.
-         * @return The builder to allow chaining commands.
-         */
-        public Builder addInterface(String interfaceName) {
-            if (TextUtils.isEmpty(interfaceName)) {
-                throw new IllegalArgumentException("interfaceName cannot be empty");
-            }
-            if (!mIfaces.contains(interfaceName)) {
-                mIfaces.add(interfaceName);
-            } else {
-                Log.w(TAG, "Attempted to add interface that is already added");
-            }
-            return this;
-        }
-
-        /**
-         * Set the connection parameters to use with the options. TODO replace
-         * with more specific calls once we decide on the standard way to
-         * express parameters.
-         *
-         * @param parameters The parameters to use.
-         * @return The builder to allow chaining commands.
-         */
-        public Builder setParameters(Bundle parameters) {
-            mConnectionParams = parameters;
-            return this;
-        }
-
-        /**
-         * Generate a set of options.
-         *
-         * @return The options with the specified components.
-         */
-        public RouteOptions build() {
-            return new RouteOptions(mIfaces, mConnectionParams);
-        }
-    }
-}
diff --git a/media/java/android/media/session/RoutePlaybackControls.java b/media/java/android/media/session/RoutePlaybackControls.java
deleted file mode 100644
index 8211983..0000000
--- a/media/java/android/media/session/RoutePlaybackControls.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.session;
-
-import android.media.MediaMetadata;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-
-/**
- * A standard media control interface for Routes that support queueing and
- * transport controls. Routes may support multiple interfaces for MediaSessions
- * to interact with.
- * @hide
- */
-public final class RoutePlaybackControls {
-    private static final String TAG = "RoutePlaybackControls";
-    public static final String NAME = "android.media.session.RoutePlaybackControls";
-
-    /** @hide */
-    public static final String KEY_VALUE1 = "value1";
-
-    /** @hide */
-    public static final String CMD_FAST_FORWARD = "fastForward";
-    /** @hide */
-    public static final String CMD_GET_CURRENT_POSITION = "getCurrentPosition";
-    /** @hide */
-    public static final String CMD_GET_CAPABILITIES = "getCapabilities";
-    /** @hide */
-    public static final String CMD_PLAY_NOW = "playNow";
-    /** @hide */
-    public static final String CMD_RESUME = "resume";
-    /** @hide */
-    public static final String CMD_PAUSE = "pause";
-
-    /** @hide */
-    public static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
-    /** @hide */
-    public static final String EVENT_METADATA_CHANGE = "metadataChange";
-
-    private final RouteInterface mIface;
-
-    private RoutePlaybackControls(RouteInterface iface) {
-        mIface = iface;
-    }
-
-    /**
-     * Get a new MediaRoutePlaybackControls instance for sending commands using
-     * this interface. If the provided route doesn't support this interface null
-     * will be returned.
-     *
-     * @param route The route to send commands to.
-     * @return A MediaRoutePlaybackControls instance or null if not supported.
-     */
-    public static RoutePlaybackControls from(Route route) {
-        RouteInterface iface = route.getInterface(NAME);
-        if (iface != null) {
-            return new RoutePlaybackControls(iface);
-        }
-        return null;
-    }
-
-    /**
-     * Send a resume command to the route.
-     */
-    public void resume() {
-        mIface.sendCommand(CMD_RESUME, null, null);
-    }
-
-    /**
-     * Send a pause command to the route.
-     */
-    public void pause() {
-        mIface.sendCommand(CMD_PAUSE, null, null);
-    }
-
-    /**
-     * Send a fast forward command.
-     */
-    public void fastForward() {
-        Bundle b = new Bundle();
-        mIface.sendCommand(CMD_FAST_FORWARD, b, null);
-    }
-
-    /**
-     * Retrieves the current playback position.
-     *
-     * @param cb The callback to receive the result on.
-     */
-    public void getCurrentPosition(ResultReceiver cb) {
-        mIface.sendCommand(CMD_GET_CURRENT_POSITION, null, cb);
-    }
-
-    public void getCapabilities(ResultReceiver cb) {
-        mIface.sendCommand(CMD_GET_CAPABILITIES, null, cb);
-    }
-
-    public void addListener(Listener listener) {
-        mIface.addListener(listener);
-    }
-
-    public void addListener(Listener listener, Handler handler) {
-        mIface.addListener(listener, handler);
-    }
-
-    public void removeListener(Listener listener) {
-        mIface.removeListener(listener);
-    }
-
-    public void playNow(String content) {
-        Bundle bundle = new Bundle();
-        bundle.putString(KEY_VALUE1, content);
-        mIface.sendCommand(CMD_PLAY_NOW, bundle, null);
-    }
-
-    /**
-     * Register this event listener using {@link #addListener} to receive
-     * RoutePlaybackControl events from a session.
-     */
-    public static abstract class Listener extends RouteInterface.EventListener {
-        @Override
-        public final void onEvent(String event, Bundle args) {
-            if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
-                onPlaybackStateChange(args.getInt(KEY_VALUE1, 0));
-            } else if (EVENT_METADATA_CHANGE.equals(event)) {
-                onMetadataUpdate((MediaMetadata) args.getParcelable(KEY_VALUE1));
-            }
-        }
-
-        /**
-         * Override to handle updates to the playback state. Valid values are in
-         * {@link TransportPerformer}. TODO put playstate values somewhere more
-         * generic.
-         *
-         * @param state
-         */
-        public void onPlaybackStateChange(int state) {
-        }
-
-        /**
-         * Override to handle metadata changes for this session's media. The
-         * default supported fields are those in {@link MediaMetadata}.
-         *
-         * @param metadata
-         */
-        public void onMetadataUpdate(MediaMetadata metadata) {
-        }
-    }
-
-}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index cac8a14..423e317 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -30,7 +30,6 @@
  */
 oneway interface ITvInputClient {
     void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
-    void onAvailabilityChanged(in String inputId, boolean isAvailable);
     void onSessionReleased(int seq);
     void onSessionEvent(in String name, in Bundle args, int seq);
     void onChannelRetuned(in Uri channelUri, int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 9a6a648..6a0c592 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -18,9 +18,10 @@
 
 import android.content.ComponentName;
 import android.graphics.Rect;
+import android.media.tv.ITvInputClient;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
-import android.media.tv.ITvInputClient;
+import android.media.tv.ITvInputManagerCallback;
 import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvTrackInfo;
@@ -34,10 +35,8 @@
 interface ITvInputManager {
     List<TvInputInfo> getTvInputList(int userId);
 
-    boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
-
-    void registerCallback(in ITvInputClient client, in String inputId, int userId);
-    void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
+    void registerCallback(in ITvInputManagerCallback callback, int userId);
+    void unregisterCallback(in ITvInputManagerCallback callback, int userId);
 
     void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
@@ -56,7 +55,12 @@
 
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
+    /*
+     * All TvInputServices which want to use hardware must call this method on
+     * BOOT_COMPLETE.
+     */
+    void registerTvInputInfo(in TvInputInfo info, int deviceId);
     ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
-            int userId);
+            in TvInputInfo info, int userId);
     void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
 }
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl
similarity index 65%
copy from telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
copy to media/java/android/media/tv/ITvInputManagerCallback.aidl
index 3a02b060..5c8a0a3 100644
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
+++ b/media/java/android/media/tv/ITvInputManagerCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony;
+package android.media.tv;
 
 /**
- * Callback interface for when DTMF has been sent.
+ * Interface to receive callbacks from ITvInputManager regardless of sessions.
+ * @hide
  */
-oneway interface IThirdPartyCallSendDtmfCallback {
-    /**
-     * Called when the DTMF code has been sent.
-     */
-    void onSendDtmfCompleted();
+oneway interface ITvInputManagerCallback {
+    void onInputStateChanged(in String inputId, int state);
 }
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index c9484dd..1fdb8c5 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -24,5 +24,5 @@
  * @hide
  */
 oneway interface ITvInputServiceCallback {
-    void onAvailabilityChanged(in String inputId, boolean isAvailable);
+    void onInputStateChanged(int state);
 }
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/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 7b8f2ec..5624f3e 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -80,7 +80,7 @@
     // Attributes from XML meta data.
     private String mSetupActivity;
     private String mSettingsActivity;
-    private int mType;
+    private int mType = TYPE_VIRTUAL;
 
     /**
      * Create a new instance of the TvInputInfo class,
@@ -128,10 +128,13 @@
                 Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
                         + si.name);
             }
-            input.mType = sa.getInt(
-                    com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
-            if (DEBUG) {
-                Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+            if (pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE, si.packageName)
+                    == PackageManager.PERMISSION_GRANTED) {
+                input.mType = sa.getInt(
+                        com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
+                if (DEBUG) {
+                    Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+                }
             }
             sa.recycle();
 
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 867b0db..79a83b0 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -24,6 +24,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pools.Pool;
 import android.util.Pools.SimplePool;
@@ -37,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -65,11 +67,43 @@
      */
     public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
 
+    /**
+     * The TV input is connected.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_CONNECTED = 0;
+    /**
+     * The TV input is connected but in standby mode. It would take a while until it becomes
+     * fully ready.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
+    /**
+     * The TV input is disconnected.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_DISCONNECTED = 2;
+
     private final ITvInputManager mService;
 
-    // A mapping from an input to the list of its TvInputListenerRecords.
-    private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
-            new HashMap<String, List<TvInputListenerRecord>>();
+    private final Object mLock = new Object();
+
+    // @GuardedBy(mLock)
+    private final List<TvInputListenerRecord> mTvInputListenerRecordsList =
+            new LinkedList<TvInputListenerRecord>();
+
+    // A mapping from TV input ID to the state of corresponding input.
+    // @GuardedBy(mLock)
+    private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>();
 
     // A mapping from the sequence number of a session to its SessionCallbackRecord.
     private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
@@ -81,6 +115,8 @@
 
     private final ITvInputClient mClient;
 
+    private final ITvInputManagerCallback mCallback;
+
     private final int mUserId;
 
     /**
@@ -242,13 +278,17 @@
      */
     public abstract static class TvInputListener {
         /**
-         * This is called when the availability status of a given TV input is changed.
+         * This is called when the state of a given TV input is changed.
          *
          * @param inputId the id of the TV input.
-         * @param isAvailable {@code true} if the given TV input is available to show TV programs.
-         *            {@code false} otherwise.
+         * @param state state of the TV input. The value is one of the following:
+         * <ul>
+         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
+         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
+         * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
+         * </ul>
          */
-        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+        public void onInputStateChanged(String inputId, int state) {
         }
     }
 
@@ -265,11 +305,11 @@
             return mListener;
         }
 
-        public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
+        public void postStateChanged(final String inputId, final int state) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mListener.onAvailabilityChanged(inputId, isAvailable);
+                    mListener.onInputStateChanged(inputId, state);
                 }
             });
         }
@@ -373,22 +413,23 @@
                     record.postSessionEvent(eventType, eventArgs);
                 }
             }
-
+        };
+        mCallback = new ITvInputManagerCallback.Stub() {
             @Override
-            public void onAvailabilityChanged(String inputId, boolean isAvailable) {
-                synchronized (mTvInputListenerRecordsMap) {
-                    List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-                    if (records == null) {
-                        // Silently ignore - no listener is registered yet.
-                        return;
-                    }
-                    int recordsCount = records.size();
-                    for (int i = 0; i < recordsCount; i++) {
-                        records.get(i).postAvailabilityChanged(inputId, isAvailable);
+            public void onInputStateChanged(String inputId, int state) {
+                synchronized (mLock) {
+                    mStateMap.put(inputId, state);
+                    for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
+                        record.postStateChanged(inputId, state);
                     }
                 }
             }
         };
+        try {
+            mService.registerCallback(mCallback, mUserId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "mService.registerCallback failed: " + e);
+        }
     }
 
     /**
@@ -405,98 +446,66 @@
     }
 
     /**
-     * Returns the availability of a given TV input.
+     * Returns the state of a given TV input. It retuns one of the following:
+     * <ul>
+     * <li>{@link #INPUT_STATE_CONNECTED}
+     * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
+     * <li>{@link #INPUT_STATE_DISCONNECTED}
+     * </ul>
      *
      * @param inputId the id of the TV input.
-     * @throws IllegalArgumentException if the argument is {@code null}.
-     * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
-     *             TV input.
+     * @throws IllegalArgumentException if the argument is {@code null} or if there is no
+     *        {@link TvInputInfo} corresponding to {@code inputId}.
      */
-    public boolean getAvailability(String inputId) {
+    public int getInputState(String inputId) {
         if (inputId == null) {
             throw new IllegalArgumentException("id cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null || records.size() == 0) {
-                throw new IllegalStateException("At least one listener should be registered.");
+        synchronized (mLock) {
+            Integer state = mStateMap.get(inputId);
+            if (state == null) {
+                throw new IllegalArgumentException("Unrecognized input ID: " + inputId);
             }
-        }
-        try {
-            return mService.getAvailability(mClient, inputId, mUserId);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
+            return state.intValue();
         }
     }
 
     /**
-     * Registers a {@link TvInputListener} for a given TV input.
+     * Registers a {@link TvInputListener}.
      *
-     * @param inputId the id of the TV input.
-     * @param listener a listener used to monitor status of the given TV input.
+     * @param listener a listener used to monitor status of the TV inputs.
      * @param handler a {@link Handler} that the status change will be delivered to.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
-     * @hide
      */
-    public void registerListener(String inputId, TvInputListener listener, Handler handler) {
-        if (inputId == null) {
-            throw new IllegalArgumentException("id cannot be null");
-        }
+    public void registerListener(TvInputListener listener, Handler handler) {
         if (listener == null) {
             throw new IllegalArgumentException("listener cannot be null");
         }
         if (handler == null) {
             throw new IllegalArgumentException("handler cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null) {
-                records = new ArrayList<TvInputListenerRecord>();
-                mTvInputListenerRecordsMap.put(inputId, records);
-                try {
-                    mService.registerCallback(mClient, inputId, mUserId);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            records.add(new TvInputListenerRecord(listener, handler));
+        synchronized (mLock) {
+            mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler));
         }
     }
 
     /**
-     * Unregisters the existing {@link TvInputListener} for a given TV input.
+     * Unregisters the existing {@link TvInputListener}.
      *
-     * @param inputId the id of the TV input.
-     * @param listener the existing listener to remove for the given TV input.
+     * @param listener the existing listener to remove.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
-     * @hide
      */
-    public void unregisterListener(String inputId, final TvInputListener listener) {
-        if (inputId == null) {
-            throw new IllegalArgumentException("id cannot be null");
-        }
+    public void unregisterListener(final TvInputListener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("listener cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null) {
-                Log.e(TAG, "No listener found for " + inputId);
-                return;
-            }
-            for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+        synchronized (mLock) {
+            for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator();
+                    it.hasNext(); ) {
                 TvInputListenerRecord record = it.next();
                 if (record.getListener() == listener) {
                     it.remove();
-                }
-            }
-            if (records.isEmpty()) {
-                try {
-                    mService.unregisterCallback(mClient, inputId, mUserId);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                } finally {
-                    mTvInputListenerRecordsMap.remove(inputId);
+                    break;
                 }
             }
         }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index a994f54..3206320 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -82,20 +82,9 @@
      */
     public static final String SERVICE_META_DATA = "android.media.tv.input";
 
-    private String mId;
     private final Handler mHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
             new RemoteCallbackList<ITvInputServiceCallback>();
-    // STOPSHIP: Redesign the API around the availability change. For now, the service will be
-    // always available.
-    private final boolean mAvailable = true;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mId = TvInputInfo.generateInputIdForComponentName(
-                new ComponentName(getPackageName(), getClass().getName()));
-    }
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -104,13 +93,6 @@
             public void registerCallback(ITvInputServiceCallback cb) {
                 if (cb != null) {
                     mCallbacks.register(cb);
-                    // The first time a callback is registered, the service needs to report its
-                    // availability status so that the system can know its initial value.
-                    try {
-                        cb.onAvailabilityChanged(mId, mAvailable);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "error in onAvailabilityChanged", e);
-                    }
                 }
             }
 
@@ -733,7 +715,6 @@
     @SuppressLint("HandlerLeak")
     private final class ServiceHandler extends Handler {
         private static final int DO_CREATE_SESSION = 1;
-        private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
 
         @Override
         public final void handleMessage(Message msg) {
@@ -759,20 +740,6 @@
                     args.recycle();
                     return;
                 }
-                case DO_BROADCAST_AVAILABILITY_CHANGE: {
-                    boolean isAvailable = (Boolean) msg.obj;
-                    int n = mCallbacks.beginBroadcast();
-                    try {
-                        for (int i = 0; i < n; i++) {
-                            mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Unexpected exception", e);
-                    } finally {
-                        mCallbacks.finishBroadcast();
-                    }
-                    return;
-                }
                 default: {
                     Log.w(TAG, "Unhandled message code: " + msg.what);
                     return;
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 6a835d6a..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,
@@ -449,13 +453,13 @@
             byteBuffer, orderID, nativeByteOrderObj);
     env->DeleteLocalRef(me);
     me = env->CallObjectMethod(
-            byteBuffer, positionID,
-            input ? 0 : buffer->offset());
-    env->DeleteLocalRef(me);
-    me = env->CallObjectMethod(
             byteBuffer, limitID,
             input ? buffer->capacity() : (buffer->offset() + buffer->size()));
     env->DeleteLocalRef(me);
+    me = env->CallObjectMethod(
+            byteBuffer, positionID,
+            input ? 0 : buffer->offset());
+    env->DeleteLocalRef(me);
     me = NULL;
 
     env->DeleteLocalRef(nativeByteOrderObj);
@@ -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/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 3dbf77b..52e9910 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -36,6 +36,8 @@
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/NuMediaExtractor.h>
 
+#include <nativehelper/ScopedLocalRef.h>
+
 #include "android_util_Binder.h"
 
 namespace android {
@@ -206,12 +208,12 @@
     size_t dstSize;
     jbyteArray byteArray = NULL;
 
-    if (dst == NULL) {
-        jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
-        CHECK(byteBufClass != NULL);
+    ScopedLocalRef<jclass> byteBufClass(env, env->FindClass("java/nio/ByteBuffer"));
+    CHECK(byteBufClass.get() != NULL);
 
+    if (dst == NULL) {
         jmethodID arrayID =
-            env->GetMethodID(byteBufClass, "array", "()[B");
+            env->GetMethodID(byteBufClass.get(), "array", "()[B");
         CHECK(arrayID != NULL);
 
         byteArray =
@@ -251,6 +253,24 @@
 
     *sampleSize = buffer->size();
 
+    jmethodID positionID = env->GetMethodID(
+            byteBufClass.get(), "position", "(I)Ljava/nio/Buffer;");
+
+    CHECK(positionID != NULL);
+
+    jmethodID limitID = env->GetMethodID(
+            byteBufClass.get(), "limit", "(I)Ljava/nio/Buffer;");
+
+    CHECK(limitID != NULL);
+
+    jobject me = env->CallObjectMethod(
+            byteBuf, limitID, offset + *sampleSize);
+    env->DeleteLocalRef(me);
+    me = env->CallObjectMethod(
+            byteBuf, positionID, offset);
+    env->DeleteLocalRef(me);
+    me = NULL;
+
     return OK;
 }
 
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 1685a447..5646740 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -157,6 +157,10 @@
         return;
     }
     sp<Camera> c = get_native_camera(env, camera, NULL);
+    if (c == NULL) {
+        // get_native_camera will throw an exception in this case
+        return;
+    }
     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
     process_media_recorder_call(env, mr->setCamera(c->remote(), c->getRecordingProxy()),
             "java/lang/RuntimeException", "setCamera failed.");
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 223013f..9a62864 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -69,12 +69,12 @@
         <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>
 
         <receiver
-            android:name=".NotificationController$NotificationBroadcastReceiver"
+            android:name=".model.NotificationController$NotificationBroadcastReceiver"
             android:exported="false" >
         </receiver>
 
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/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index c17c73b..faad527 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -42,4 +42,7 @@
 
     <integer name="print_option_column_count">2</integer>
 
+    <fraction name="page_selected_alpha">100%</fraction>
+    <fraction name="page_unselected_alpha">50%</fraction>
+
 </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/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index d37ccc0..3134e93 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -56,8 +56,6 @@
     private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
 
     private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
-    private static final String EXTRA_PRINTJOB_LABEL = "EXTRA_PRINTJOB_LABEL";
-    private static final String EXTRA_PRINTER_NAME = "EXTRA_PRINTER_NAME";
 
     private final Context mContext;
     private final NotificationManager mNotificationManager;
@@ -69,7 +67,7 @@
     }
 
     public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
-        List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
+        List<PrintJobInfo> notifyPrintJobs = new ArrayList<>();
 
         final int printJobCount = printJobs.size();
         for (int i = 0; i < printJobCount; i++) {
@@ -252,8 +250,6 @@
         Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
         intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + printJob.getId().flattenToString());
         intent.putExtra(EXTRA_PRINT_JOB_ID, printJob.getId());
-        intent.putExtra(EXTRA_PRINTJOB_LABEL, printJob.getLabel());
-        intent.putExtra(EXTRA_PRINTER_NAME, printJob.getPrinterName());
         return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
     }
 
@@ -302,17 +298,14 @@
             String action = intent.getAction();
             if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) {
                 PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
-                String printJobLabel = intent.getExtras().getString(EXTRA_PRINTJOB_LABEL);
-                String printerName = intent.getExtras().getString(EXTRA_PRINTER_NAME);
-                handleCancelPrintJob(context, printJobId, printJobLabel, printerName);
+                handleCancelPrintJob(context, printJobId);
             } else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) {
                 PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
                 handleRestartPrintJob(context, printJobId);
             }
         }
 
-        private void handleCancelPrintJob(final Context context, final PrintJobId printJobId,
-                final String printJobLabel, final String printerName) {
+        private void handleCancelPrintJob(final Context context, final PrintJobId printJobId) {
             if (DEBUG) {
                 Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
             }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index d802cd8..8a65a2e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -27,6 +27,7 @@
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 import android.printservice.PrintServiceInfo;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -68,10 +69,10 @@
     private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
 
     private final List<PrinterInfo> mPrinters =
-            new ArrayList<PrinterInfo>();
+            new ArrayList<>();
 
     private final List<PrinterInfo> mFavoritePrinters =
-            new ArrayList<PrinterInfo>();
+            new ArrayList<>();
 
     private final PersistenceManager mPersistenceManager;
 
@@ -92,7 +93,7 @@
 
     private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters,
             ArrayMap<PrinterId, PrinterInfo> favoritePrinters) {
-        List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+        List<PrinterInfo> printers = new ArrayList<>();
 
         // Add the updated favorite printers.
         final int favoritePrinterCount = favoritePrinters.size();
@@ -142,7 +143,7 @@
         // The contract is that if we already have a valid,
         // result the we have to deliver it immediately.
         if (!mPrinters.isEmpty()) {
-            deliverResult(new ArrayList<PrinterInfo>(mPrinters));
+            deliverResult(new ArrayList<>(mPrinters));
         }
         // Always load the data to ensure discovery period is
         // started and to make sure obsolete printers are updated.
@@ -184,11 +185,12 @@
                                 + mDiscoverySession.getPrinters().size()
                                 + " " + FusedPrintersProvider.this.hashCode());
                     }
+
                     updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
                 }
             });
             final int favoriteCount = mFavoritePrinters.size();
-            List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
+            List<PrinterId> printerIds = new ArrayList<>(favoriteCount);
             for (int i = 0; i < favoriteCount; i++) {
                 printerIds.add(mFavoritePrinters.get(i).getId());
             }
@@ -208,16 +210,19 @@
 
         mPrintersUpdatedBefore = true;
 
-        ArrayMap<PrinterId, PrinterInfo> printersMap =
-                new ArrayMap<PrinterId, PrinterInfo>();
+        // Some of the found printers may have be a printer that is in the
+        // history but with its name changed. Hence, we try to update the
+        // printer to use its current name instead of the historical one.
+        mPersistenceManager.updatePrintersHistoricalNamesIfNeeded(printers);
+
+        ArrayMap<PrinterId, PrinterInfo> printersMap = new ArrayMap<>();
         final int printerCount = printers.size();
         for (int i = 0; i < printerCount; i++) {
             PrinterInfo printer = printers.get(i);
             printersMap.put(printer.getId(), printer);
         }
 
-        ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap =
-                new ArrayMap<PrinterId, PrinterInfo>();
+        ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap = new ArrayMap<>();
         final int favoritePrinterCount = favoritePrinters.size();
         for (int i = 0; i < favoritePrinterCount; i++) {
             PrinterInfo favoritePrinter = favoritePrinters.get(i);
@@ -271,6 +276,10 @@
         onStopLoading();
     }
 
+    public boolean areHistoricalPrintersLoaded() {
+        return mPersistenceManager.mReadHistoryCompleted;
+    }
+
     public void setTrackedPrinter(PrinterId printerId) {
         if (isStarted() && mDiscoverySession != null
                 && mDiscoverySession.isPrinterDiscoveryStarted()) {
@@ -306,7 +315,7 @@
         for (int i = 0; i < favoritePrinterCount; i++) {
             PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
             if (favoritePrinter.getId().equals(printerId)) {
-                newFavoritePrinters = new ArrayList<PrinterInfo>();
+                newFavoritePrinters = new ArrayList<>();
                 newFavoritePrinters.addAll(mPrinters);
                 newFavoritePrinters.remove(i);
                 break;
@@ -340,7 +349,7 @@
 
         private final AtomicFile mStatePersistFile;
 
-        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>();
+        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<>();
 
         private boolean mReadHistoryCompleted;
         private boolean mReadHistoryInProgress;
@@ -378,17 +387,42 @@
             mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
         }
 
-        @SuppressWarnings("unchecked")
+        public void updatePrintersHistoricalNamesIfNeeded(List<PrinterInfo> printers) {
+            boolean writeHistory = false;
+
+            final int printerCount = printers.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo printer = printers.get(i);
+                writeHistory |= renamePrinterIfNeeded(printer);
+            }
+
+            if (writeHistory) {
+                writePrinterHistory();
+            }
+        }
+
+        public boolean renamePrinterIfNeeded(PrinterInfo printer) {
+            boolean renamed = false;
+            final int printerCount = mHistoricalPrinters.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
+                if (historicalPrinter.getId().equals(printer.getId())
+                        && !TextUtils.equals(historicalPrinter.getName(), printer.getName())) {
+                    mHistoricalPrinters.set(i, printer);
+                    renamed = true;
+                }
+            }
+            return renamed;
+        }
+
         public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
             if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
                 mHistoricalPrinters.remove(0);
             }
             mHistoricalPrinters.add(printer);
-            new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                    new ArrayList<PrinterInfo>(mHistoricalPrinters));
+            writePrinterHistory();
         }
 
-        @SuppressWarnings("unchecked")
         public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
             boolean writeHistory = false;
             final int printerCount = mHistoricalPrinters.size();
@@ -400,18 +434,22 @@
                 }
             }
             if (writeHistory) {
-                new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                        new ArrayList<PrinterInfo>(mHistoricalPrinters));
+                writePrinterHistory();
             }
         }
 
+        @SuppressWarnings("unchecked")
+        private void writePrinterHistory() {
+            new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+                    new ArrayList<>(mHistoricalPrinters));
+        }
+
         public boolean isHistoryChanged() {
             return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
         }
 
         private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
-            Map<PrinterId, PrinterRecord> recordMap =
-                    new ArrayMap<PrinterId, PrinterRecord>();
+            Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<>();
 
             // Recompute the weights.
             float currentWeight = 1.0f;
@@ -429,14 +467,14 @@
             }
 
             // Soft the favorite printers.
-            List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
+            List<PrinterRecord> favoriteRecords = new ArrayList<>(
                     recordMap.values());
             Collections.sort(favoriteRecords);
 
             // Write the favorites to the output.
             final int favoriteCount = Math.min(favoriteRecords.size(),
                     MAX_FAVORITE_PRINTER_COUNT);
-            List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
+            List<PrinterInfo> favoritePrinters = new ArrayList<>(favoriteCount);
             for (int i = 0; i < favoriteCount; i++) {
                 PrinterInfo printer = favoriteRecords.get(i).printer;
                 favoritePrinters.add(printer);
@@ -478,7 +516,7 @@
                 List<PrintServiceInfo> services = printManager
                         .getEnabledPrintServices();
 
-                Set<ComponentName> enabledComponents = new ArraySet<ComponentName>();
+                Set<ComponentName> enabledComponents = new ArraySet<>();
                 final int installedServiceCount = services.size();
                 for (int i = 0; i < installedServiceCount; i++) {
                     ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
@@ -524,28 +562,23 @@
                         Log.i(LOG_TAG, "No existing printer history "
                                 + FusedPrintersProvider.this.hashCode());
                     }
-                    return new ArrayList<PrinterInfo>();
+                    return new ArrayList<>();
                 }
                 try {
-                    List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+                    List<PrinterInfo> printers = new ArrayList<>();
                     XmlPullParser parser = Xml.newPullParser();
                     parser.setInput(in, null);
                     parseState(parser, printers);
                     // Take a note which version of the history was read.
                     mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
                     return printers;
-                } catch (IllegalStateException ise) {
-                    Slog.w(LOG_TAG, "Failed parsing ", ise);
-                } catch (NullPointerException npe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", npe);
-                } catch (NumberFormatException nfe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", nfe);
-                } catch (XmlPullParserException xppe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", xppe);
-                } catch (IOException ioe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", ioe);
-                } catch (IndexOutOfBoundsException iobe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", iobe);
+                } catch (IllegalStateException
+                        | NullPointerException
+                        | NumberFormatException
+                        | XmlPullParserException
+                        | IOException
+                        | IndexOutOfBoundsException e) {
+                    Slog.w(LOG_TAG, "Failed parsing ", e);
                 } finally {
                     IoUtils.closeQuietly(in);
                 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 8885a7b..30808ba 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -90,7 +90,10 @@
     private int mSelectedPageCount;
 
     private float mSelectedPageElevation;
+    private float mSelectedPageAlpha;
+
     private float mUnselectedPageElevation;
+    private float mUnselectedPageAlpha;
 
     private int mPreviewPageMargin;
     private int mPreviewListPadding;
@@ -127,8 +130,13 @@
 
         mSelectedPageElevation = mContext.getResources().getDimension(
                 R.dimen.selected_page_elevation);
+        mSelectedPageAlpha = mContext.getResources().getFraction(
+                R.fraction.page_selected_alpha, 1, 1);
+
         mUnselectedPageElevation = mContext.getResources().getDimension(
                 R.dimen.unselected_page_elevation);
+        mUnselectedPageAlpha = mContext.getResources().getFraction(
+                R.fraction.page_unselected_alpha, 1, 1);
 
         mPreviewPageMargin = mContext.getResources().getDimensionPixelSize(
                 R.dimen.preview_page_margin);
@@ -326,22 +334,24 @@
         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);
         }
 
         TextView pageNumberView = (TextView) page.findViewById(R.id.page_number);
@@ -379,6 +389,7 @@
             mSelectedPages = selectedPages;
             mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
                     mSelectedPages, mDocumentPageCount);
+            updatePreviewAreaAndPageSize();
             notifyDataSetChanged();
         }
         return mSelectedPages;
@@ -756,15 +767,17 @@
             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);
-                page.animate().translationZ(mSelectedPageElevation);
+                pageSelector.setSelected(true);
+                page.animate().translationZ(mSelectedPageElevation)
+                        .alpha(mSelectedPageAlpha);
             } else {
                 mConfirmedPagesInDocument.remove(pageInDocument);
-                pageSelector.setChecked(false);
-                page.animate().translationZ(mUnselectedPageElevation);
+                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 5ec2111..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;
 
@@ -190,12 +189,14 @@
 
     private MediaSizeComparator mMediaSizeComparator;
 
-    private PrinterInfo mOldCurrentPrinter;
+    private PrinterInfo mCurrentPrinter;
 
     private PageRange[] mSelectedPages;
 
     private String mCallingPackageName;
 
+    private int mCurrentPageCount;
+
     private int mState;
 
     private int mUiState = UI_STATE_PREVIEW;
@@ -576,7 +577,7 @@
             }
         }
 
-        PrinterId printerId = mOldCurrentPrinter.getId();
+        PrinterId printerId = mCurrentPrinter.getId();
         final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
         mDestinationSpinner.setSelection(index);
     }
@@ -810,7 +811,7 @@
     }
 
     private void requestCreatePdfFileOrFinish() {
-        if (getCurrentPrinter() == mDestinationSpinnerAdapter.getPdfPrinter()) {
+        if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
             startCreateDocumentActivity();
         } else {
             finish();
@@ -898,19 +899,14 @@
     }
 
     private void addCurrentPrinterToHistory() {
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter != null) {
+        if (mCurrentPrinter != null) {
             PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
-            if (!currentPrinter.getId().equals(fakePdfPrinterId)) {
-                mPrinterRegistry.addHistoricalPrinter(currentPrinter);
+            if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) {
+                mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter);
             }
         }
     }
 
-    private PrinterInfo getCurrentPrinter() {
-        return ((PrinterHolder) mDestinationSpinner.getSelectedItem()).printer;
-    }
-
     private void cancelPrint() {
         setState(STATE_PRINT_CANCELED);
         updateOptionsUi();
@@ -970,7 +966,6 @@
         mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
         mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
         mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
-        mDestinationSpinner.setSelection(0);
 
         // Media size.
         mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
@@ -1002,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);
@@ -1036,16 +1022,14 @@
         @Override
         public void onClick(View view) {
             if (view == mPrintButton) {
-                PrinterInfo currentPrinter = getCurrentPrinter();
-                if (currentPrinter != null) {
+                if (mCurrentPrinter != null) {
                     confirmPrint();
                 } else {
                     cancelPrint();
                 }
             } else if (view == mMoreOptionsButton) {
-                PrinterInfo currentPrinter = getCurrentPrinter();
-                if (currentPrinter != null) {
-                    startAdvancedPrintOptionsActivity(currentPrinter);
+                if (mCurrentPrinter != null) {
+                    startAdvancedPrintOptionsActivity(mCurrentPrinter);
                 }
             }
         }
@@ -1090,8 +1074,7 @@
 
         // If no current printer, or it has no capabilities, or it is not
         // available, we disable all print options except the destination.
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter == null || !canPrint(currentPrinter)) {
+        if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
             mCopiesEditText.setEnabled(false);
             mMediaSizeSpinner.setEnabled(false);
             mColorModeSpinner.setEnabled(false);
@@ -1103,7 +1086,7 @@
             return;
         }
 
-        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
         PrintAttributes defaultAttributes = capabilities.getDefaults();
 
         // Destination.
@@ -1274,24 +1257,25 @@
                     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 = currentPrinter.getId().getServiceName();
+        ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
         if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
                 this, serviceName))) {
             mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
@@ -1302,7 +1286,7 @@
         }
 
         // Print
-        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
             mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
         } else {
             mPrintButton.setImageResource(com.android.internal.R.drawable.ic_menu_save);
@@ -1317,7 +1301,7 @@
         }
 
         // Copies
-        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
             mCopiesEditText.setEnabled(true);
         } else {
             mCopiesEditText.setEnabled(false);
@@ -1329,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;
@@ -1395,8 +1400,7 @@
     }
 
     public void onPrinterAvailable(PrinterInfo printer) {
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter.equals(printer)) {
+        if (mCurrentPrinter.equals(printer)) {
             setState(STATE_CONFIGURING);
             if (canUpdateDocument()) {
                 updateDocument(true, false);
@@ -1407,7 +1411,7 @@
     }
 
     public void onPrinterUnavailable(PrinterInfo printer) {
-        if (getCurrentPrinter().getId().equals(printer.getId())) {
+        if (mCurrentPrinter.getId().equals(printer.getId())) {
             setState(STATE_PRINTER_UNAVAILABLE);
             if (mPrintedDocument.isUpdating()) {
                 mPrintedDocument.cancel();
@@ -1444,15 +1448,14 @@
             return false;
         }
 
-        PrinterInfo currentPrinter = getCurrentPrinter();
-        if (currentPrinter == null) {
+        if (mCurrentPrinter == null) {
             return false;
         }
-        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
         if (capabilities == null) {
             return false;
         }
-        if (currentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+        if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
             return false;
         }
 
@@ -1560,8 +1563,13 @@
 
         private final PrinterHolder mFakePdfPrinterHolder;
 
+        private boolean mHistoricalPrintersLoaded;
+
         public DestinationAdapter() {
-            addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+            mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
+            if (mHistoricalPrintersLoaded) {
+                addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+            }
             mPrinterRegistry.setOnPrintersChangeListener(this);
             mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
         }
@@ -1602,7 +1610,10 @@
 
         @Override
         public int getCount() {
-            return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+            if (mHistoricalPrintersLoaded) {
+                return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+            }
+            return 0;
         }
 
         @Override
@@ -1731,6 +1742,12 @@
             // not shown in the initial short list. Therefore, we have
             // to keep the printer order.
 
+            // Check if historical printers are loaded as this adapter is open
+            // for busyness only if they are. This member is updated here and
+            // when the adapter is created because the historical printers may
+            // be loaded before or after the adapter is created.
+            mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
+
             // No old printers - do not bother keeping their position.
             if (mPrinterHolders.isEmpty()) {
                 addPrinters(mPrinterHolders, printers);
@@ -1839,7 +1856,7 @@
     private final class PrintersObserver extends DataSetObserver {
         @Override
         public void onChanged() {
-            PrinterInfo oldPrinterState = mOldCurrentPrinter;
+            PrinterInfo oldPrinterState = mCurrentPrinter;
             if (oldPrinterState == null) {
                 return;
             }
@@ -1927,14 +1944,15 @@
                     return;
                 }
 
-                PrinterInfo currentPrinter = getCurrentPrinter();
+                PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem();
+                PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null;
 
                 // Why on earth item selected is called if no selection changed.
-                if (mOldCurrentPrinter == currentPrinter) {
+                if (mCurrentPrinter == currentPrinter) {
                     return;
                 }
 
-                mOldCurrentPrinter = currentPrinter;
+                mCurrentPrinter = currentPrinter;
 
                 PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
                         currentPrinter.getId());
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
index 7816d66..a3d7f01 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
@@ -83,6 +83,10 @@
         getPrinterProvider().setTrackedPrinter(printerId);
     }
 
+    public boolean areHistoricalPrintersLoaded() {
+        return getPrinterProvider().areHistoricalPrintersLoaded();
+    }
+
     private FusedPrintersProvider getPrinterProvider() {
         Loader<?> loader = mActivity.getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
         return (FusedPrintersProvider) loader;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index afdbb2a..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;
 
 /**
@@ -147,6 +148,7 @@
         mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
 
         mExpandCollapseHandle.setOnClickListener(this);
+        mSummaryContent.setOnClickListener(this);
 
         // Make sure we start in a closed options state.
         onDragProgress(1.0f);
@@ -154,7 +156,7 @@
 
     @Override
     public void onClick(View view) {
-        if (view == mExpandCollapseHandle) {
+        if (view == mExpandCollapseHandle || view == mSummaryContent) {
             if (isOptionsClosed() && mOptionsStateController.canOpenOptions()) {
                 openOptions();
             } else if (isOptionsOpened() && mOptionsStateController.canCloseOptions()) {
@@ -300,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)) {
@@ -350,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/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index e5606c7..51e2c95 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -19,8 +19,12 @@
 
     <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
     <string name="bugreport_finished_title">Bug report captured</string>
+
+    <!-- Text of notification indicating that swipe left will share the captured bugreport. [CHAR LIMIT=100] -->
+    <string name="bugreport_finished_text" product="watch">Swipe left to share your bug report</string>
     <!-- Text of notification indicating that touching will share the captured bugreport. [CHAR LIMIT=100] -->
-    <string name="bugreport_finished_text">Touch to share your bug report</string>
+    <string name="bugreport_finished_text" product="default">Touch to share your bug report</string>
+
 
     <!-- Body of dialog informing user about contents of a bugreport. [CHAR LIMIT=NONE] -->
     <string name="bugreport_confirm">Bug reports contain data from the system\'s various log files, including personal and private information.  Only share bug reports with apps and people you trust.</string>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml
deleted file mode 100644
index a6c2cf8..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M10.25,1.75c-0.6,-0.6 -1.5,-0.6 -2.1,0.0l-6.4,6.4c-0.6,0.6 -0.6,1.5 0.0,2.1l12.0,12.0c0.6,0.6 1.5,0.6 2.1,0.0l6.4,-6.4c0.6,-0.6 0.6,-1.5 0.0,-2.1L10.25,1.75zM14.85,21.25l-12.0,-12.0l6.4,-6.4l12.0,12.0L14.85,21.25z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M16.55,2.5c3.3,1.5 5.6,4.7 6.0,8.5l1.5,0.0c-0.6,-6.2 -5.7,-11.0 -12.0,-11.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.55,2.5z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M7.55,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5l-1.4,0.0c0.5,6.2 5.6,11.0 11.9,11.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.55,21.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml
deleted file mode 100644
index 4107c46..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M10.24,2.43C9.67,1.88 8.83,1.88 8.28,2.45L2.34,8.48c-0.56,0.57 -0.55,1.41 0.02,1.96l11.3,11.13c0.57,0.56 1.41,0.55 1.96,-0.02l5.93,-6.03c0.56,-0.57 0.55,-1.41 -0.02,-1.96L10.24,2.43zM14.68,20.62L3.38,9.5l5.93,-6.03l11.3,11.13L14.68,20.62z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M16.91,2.71c3.23,1.64 5.39,4.94 5.62,8.76l1.5,0.07c-0.33,-6.22 -5.21,-11.24 -11.5,-11.52c-0.2,-0.01 -0.4,-0.02 -0.7,-0.03l3.63,3.96L16.91,2.71z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M7.09,21.29c-3.23,-1.64 -5.39,-4.94 -5.62,-8.76l-1.4,-0.06c0.23,6.22 5.11,11.24 11.4,11.51c0.2,0.01 0.4,0.02 0.7,0.03l-3.63,-3.96L7.09,21.29z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml
deleted file mode 100644
index 127296c4..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.07,4.12c-0.43,-0.49 -1.11,-0.53 -1.6,-0.1L4.3,8.57C3.81,9.0 3.77,9.68 4.19,10.17l8.54,9.71c0.43,0.49 1.11,0.53 1.6,0.1l5.18,-4.55c0.49,-0.43 0.53,-1.11 0.1,-1.6L11.07,4.12zM13.61,19.17L5.08,9.46l5.18,-4.55l8.54,9.71L13.61,19.17z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M17.72,3.2c3.06,1.94 4.9,5.43 4.77,9.25l1.49,0.21C24.23,6.43 19.84,0.97 13.6,0.11c-0.2,-0.03 -0.4,-0.06 -0.69,-0.1l3.24,4.29L17.72,3.2z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M6.19,20.78c-3.06,-1.94 -4.9,-5.43 -4.77,-9.25l-1.39,-0.19c-0.36,6.21 4.03,11.67 10.27,12.53c0.2,0.03 0.4,0.06 0.69,0.1l-3.24,-4.29L6.19,20.78z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml
deleted file mode 100644
index d00262ab..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.88,5.72c-0.3,-0.42 -0.83,-0.51 -1.25,-0.21L6.19,8.69c-0.42,0.3 -0.51,0.83 -0.21,1.25l5.95,8.34c0.3,0.42 0.83,0.51 1.25,0.21l4.45,-3.17c0.42,-0.3 0.51,-0.83 0.21,-1.25L11.88,5.72zM12.68,17.79L6.73,9.45l4.45,-3.17l5.95,8.34L12.68,17.79z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M18.8,4.01c2.79,2.32 4.16,6.01 3.54,9.78l1.45,0.4c1.06,-6.14 -2.59,-12.11 -8.67,-13.78c-0.19,-0.05 -0.39,-0.11 -0.68,-0.18l2.66,4.67L18.8,4.01z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M5.11,19.96c-2.79,-2.32 -4.16,-6.01 -3.54,-9.78L0.21,9.81c-1.15,6.11 2.5,12.09 8.57,13.75c0.19,0.05 0.39,0.11 0.68,0.18L6.8,19.08L5.11,19.96z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml
deleted file mode 100644
index 570f51f..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.41,8.14c0.05,-0.42 -0.23,-0.78 -0.65,-0.83l-4.5,-0.54C9.84,6.72 9.48,7.0 9.43,7.42l-1.01,8.44c-0.05,0.42 0.23,0.78 0.65,0.83l4.5,0.54C14.0,17.28 14.35,17.0 14.4,16.58L15.41,8.14zM9.16,15.99l1.01,-8.44l4.5,0.54l-1.01,8.44L9.16,15.99z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M22.35,10.28c0.64,3.57 -0.69,7.28 -3.59,9.77l0.85,1.23c4.76,-4.01 5.82,-10.94 2.24,-16.12c-0.11,-0.16 -0.23,-0.33 -0.4,-0.58l-0.97,5.29L22.35,10.28z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M1.6,13.66c-0.64,-3.57 0.69,-7.28 3.59,-9.77L4.4,2.74c-4.82,3.93 -5.88,10.86 -2.3,16.04c0.11,0.16 0.23,0.33 0.4,0.58l0.97,-5.29L1.6,13.66z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml
deleted file mode 100644
index aaf9356..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.62,13.08c0.29,-0.1 0.44,-0.39 0.34,-0.69L14.9,9.27c-0.1,-0.29 -0.39,-0.44 -0.69,-0.34l-5.86,1.98c-0.29,0.1 -0.44,0.39 -0.34,0.69l1.06,3.12c0.1,0.29 0.39,0.44 0.69,0.34L15.62,13.08zM8.51,11.44l5.86,-1.98l1.06,3.12l-5.86,1.98L8.51,11.44z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M18.53,20.21c-2.8,2.3 -6.69,2.95 -10.27,1.64L7.6,23.2c5.83,2.2 12.39,-0.26 15.16,-5.92c0.09,-0.18 0.18,-0.36 0.31,-0.63l-5.09,1.73L18.53,20.21z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M5.45,3.76c2.8,-2.3 6.69,-2.95 10.27,-1.64l0.62,-1.26C10.56,-1.42 4.0,1.04 1.22,6.69C1.13,6.87 1.05,7.05 0.91,7.32L6.0,5.59L5.45,3.76z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml
deleted file mode 100644
index 330ce6a..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M12.23,14.48c0.13,0.16 0.35,0.17 0.5,0.04l1.66,-1.4c0.16,-0.13 0.17,-0.35 0.04,-0.5l-2.62,-3.11c-0.13,-0.16 -0.35,-0.17 -0.5,-0.04l-1.66,1.4c-0.16,0.13 -0.17,0.35 -0.04,0.5L12.23,14.48zM11.53,9.73l2.62,3.11l-1.66,1.4l-2.62,-3.11L11.53,9.73z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M6.04,20.63c-3.01,-2.02 -4.75,-5.56 -4.52,-9.37l-1.48,-0.25c-0.43,6.21 3.81,11.79 10.02,12.83c0.2,0.03 0.39,0.07 0.69,0.12l-3.12,-4.37L6.04,20.63z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M18.04,3.37c3.01,2.02 4.75,5.56 4.52,9.37l1.38,0.23c0.53,-6.2 -3.71,-11.77 -9.93,-12.81c-0.2,-0.03 -0.39,-0.07 -0.69,-0.12l3.12,4.37L18.04,3.37z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml
deleted file mode 100644
index 1c7f1a1..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M10.55,12.73c-0.06,0.12 -0.02,0.25 0.1,0.32l1.26,0.68c0.12,0.06 0.25,0.02 0.32,-0.1l1.27,-2.36c0.06,-0.12 0.02,-0.25 -0.1,-0.32l-1.26,-0.68c-0.12,-0.06 -0.25,-0.02 -0.32,0.1L10.55,12.73zM13.29,11.15l-1.27,2.36l-1.26,-0.68l1.27,-2.36L13.29,11.15z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M1.55,12.95C1.18,9.34 2.78,5.74 5.86,3.47L5.1,2.18C0.05,5.83 -1.51,12.66 1.67,18.09c0.1,0.17 0.2,0.35 0.35,0.6l1.36,-5.2L1.55,12.95z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M22.5,11.12c0.37,3.61 -1.23,7.21 -4.31,9.47l0.71,1.21c5.1,-3.56 6.67,-10.39 3.48,-15.83c-0.1,-0.17 -0.2,-0.35 -0.35,-0.6l-1.36,5.2L22.5,11.12z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml
deleted file mode 100644
index ebfbad6..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M11.45,12c0,0.14,0.12,0.25,0.25,0.25l0.06,0l0,-0.5l-0.06,0C11.57,11.75,11.45,11.86,11.45,12z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.77,11.62l-0.06,0.0c-0.21,0.0 -0.37,0.17 -0.38,0.37c0.0,0.21 0.17,0.37 0.37,0.37l0.06,0.0c0.0,0.07 0.06,0.12 0.12,0.13l0.62,0.0c0.07,0.0 0.12,-0.06 0.13,-0.12l0.0,-0.75c0.0,-0.07 -0.06,-0.12 -0.12,-0.13l-0.62,0.0C11.83,11.5 11.77,11.56 11.77,11.62zM12.33,12.0c0.0,0.07 -0.06,0.12 -0.13,0.12c-0.07,0.0 -0.12,-0.06 -0.12,-0.13c0.0,-0.07 0.06,-0.12 0.13,-0.12C12.28,11.88 12.33,11.93 12.33,12.0zM11.77,11.75l0.0,0.5l-0.06,0.0c-0.14,0.0 -0.26,-0.11 -0.25,-0.25c0.0,-0.14 0.12,-0.25 0.26,-0.25L11.77,11.75z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M3.83,5.44c2.21,-2.87 5.85,-4.38 9.64,-3.9l0.34,-1.46C7.64,-0.75 1.81,3.12 0.37,9.25C0.32,9.45 0.28,9.64 0.21,9.93L4.78,7.1L3.83,5.44zM20.28,18.53c-2.21,2.87 -5.85,4.38 -9.64,3.9l-0.32,1.36c6.15,0.93 11.99,-2.95 13.42,-9.08c0.05,-0.19 0.09,-0.39 0.16,-0.68l-4.57,2.83L20.28,18.53z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml
deleted file mode 100644
index 21dda8c..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M10.42,9.96c-0.42,0.42,-0.39,1.12,0.03,1.54l0.19,0.19l1.51,-1.53l-0.19,-0.19       C11.54,9.55,10.84,9.54,10.42,9.96z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M12.53,9.78l-0.19,-0.19c-0.63,-0.63 -1.66,-0.62 -2.28,0.02c-0.63,0.63 -0.62,1.66 0.02,2.28l0.19,0.19c-0.21,0.21 -0.21,0.55 0.01,0.76l1.92,1.89c0.21,0.21 0.55,0.21 0.76,-0.01l2.27,-2.3c0.21,-0.21 0.21,-0.55 -0.01,-0.76l-1.92,-1.89C13.08,9.56 12.74,9.56 12.53,9.78zM13.12,12.62c-0.21,0.21 -0.55,0.21 -0.76,0.01c-0.21,-0.21 -0.21,-0.55 -0.01,-0.76c0.21,-0.21 0.55,-0.21 0.76,-0.01C13.33,12.07 13.33,12.41 13.12,12.62zM12.15,10.16l-1.51,1.53l-0.19,-0.19c-0.42,-0.42 -0.45,-1.12 -0.03,-1.54c0.42,-0.42 1.12,-0.41 1.54,0.01L12.15,10.16z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M7.82,2.36c3.3,-1.49 7.23,-1.12 10.35,1.09l0.99,-1.13C14.09,-1.32 7.12,-0.64 2.97,4.1C2.84,4.25 2.71,4.4 2.51,4.62l5.36,-0.36L7.82,2.36zM16.18,21.64c-3.3,1.49 -7.23,1.12 -10.35,-1.09l-0.92,1.05c4.99,3.71 11.97,3.03 16.12,-1.71c0.13,-0.15 0.26,-0.3 0.46,-0.53l-5.36,0.36L16.18,21.64z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml
deleted file mode 100644
index f4186fe..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M10.53,8.85c-0.7,0.37,-0.95,1.28,-0.58,1.98l0.17,0.32l2.55,-1.36L12.5,9.48       C12.12,8.78,11.23,8.48,10.53,8.85z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M13.3,9.46l-0.17,-0.32c-0.56,-1.05 -1.87,-1.45 -2.93,-0.89s-1.45,1.87 -0.89,2.93l0.17,0.32c-0.35,0.19 -0.48,0.62 -0.3,0.98l1.7,3.18c0.19,0.35 0.62,0.48 0.98,0.3l3.82,-2.04c0.35,-0.19 0.48,-0.62 0.3,-0.98l-1.7,-3.18C14.09,9.4 13.65,9.27 13.3,9.46zM12.92,13.34c-0.35,0.19 -0.79,0.06 -0.98,-0.3c-0.19,-0.35 -0.05,-0.79 0.3,-0.98c0.35,-0.19 0.79,-0.05 0.98,0.3C13.41,12.72 13.27,13.15 12.92,13.34zM12.67,9.8l-2.55,1.36l-0.17,-0.32c-0.37,-0.7 -0.13,-1.61 0.58,-1.98c0.7,-0.37 1.59,-0.08 1.97,0.63L12.67,9.8z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M11.48,1.5c3.62,-0.23 7.16,1.5 9.3,4.66l1.32,-0.71C18.65,0.27 11.89,-1.55 6.34,1.42C6.16,1.51 5.98,1.61 5.72,1.75l5.14,1.56L11.48,1.5zM12.52,22.5c-3.62,0.23 -7.16,-1.5 -9.3,-4.66L1.98,18.5c3.37,5.23 10.13,7.06 15.68,4.08c0.18,-0.09 0.35,-0.19 0.62,-0.33l-5.14,-1.56L12.52,22.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml
deleted file mode 100644
index d408e28..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M10.83,8.49c-0.91,0.3,-1.39,1.31,-1.09,2.22l0.13,0.41l3.28,-1.08l-0.13,-0.41       C12.72,8.72,11.73,8.19,10.83,8.49z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M13.98,9.77l-0.13,-0.41C13.4,8.0 11.93,7.26 10.57,7.71c-1.36,0.45 -2.1,1.91 -1.65,3.27l0.13,0.41c-0.45,0.15 -0.7,0.64 -0.55,1.09l1.35,4.1c0.15,0.45 0.64,0.7 1.09,0.55l4.92,-1.62c0.45,-0.15 0.7,-0.64 0.55,-1.09l-1.35,-4.1C14.92,9.87 14.43,9.62 13.98,9.77zM12.73,14.27c-0.45,0.15 -0.94,-0.1 -1.09,-0.55c-0.15,-0.45 0.1,-0.94 0.55,-1.09c0.45,-0.15 0.94,0.1 1.09,0.55C13.43,13.64 13.18,14.12 12.73,14.27zM13.16,10.04l-3.28,1.08l-0.13,-0.41c-0.3,-0.91 0.18,-1.92 1.09,-2.22c0.91,-0.3 1.9,0.24 2.19,1.14L13.16,10.04z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M13.55,1.6c3.59,0.48 6.72,2.87 8.21,6.39l1.44,-0.44C20.82,1.8 14.54,-1.31 8.51,0.52c-0.19,0.06 -0.38,0.12 -0.67,0.2l4.74,2.53L13.55,1.6zM10.45,22.4c-3.59,-0.48 -6.72,-2.87 -8.21,-6.39L0.9,16.41c2.28,5.79 8.55,8.9 14.58,7.07c0.19,-0.06 0.38,-0.12 0.67,-0.2l-4.74,-2.53L10.45,22.4z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml
deleted file mode 100644
index 1ac6b39..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M11.17,7.71c-1.09,0.19,-1.8,1.28,-1.61,2.37l0.09,0.49l3.94,-0.7l-0.09,-0.49C13.3,8.3,12.26,7.52,11.17,7.71       z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M14.57,9.7l-0.09,-0.49C14.19,7.58 12.63,6.49 11.0,6.78c-1.63,0.29 -2.71,1.85 -2.43,3.48l0.08,0.49c-0.54,0.1 -0.91,0.62 -0.81,1.16l0.87,4.92c0.1,0.54 0.62,0.91 1.16,0.81l5.91,-1.05c0.54,-0.1 0.91,-0.61 0.81,-1.16l-0.87,-4.92C15.63,9.97 15.11,9.61 14.57,9.7zM12.4,14.66c-0.54,0.1 -1.06,-0.27 -1.16,-0.81c-0.1,-0.54 0.27,-1.06 0.81,-1.16s1.06,0.27 1.16,0.81C13.3,14.04 12.94,14.56 12.4,14.66zM13.58,9.88l-3.94,0.7l-0.09,-0.49c-0.19,-1.09 0.52,-2.17 1.61,-2.37s2.13,0.58 2.33,1.67L13.58,9.88z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M14.99,1.92c3.49,0.97 6.26,3.78 7.24,7.48l1.48,-0.23c-1.55,-6.03 -7.32,-9.99 -13.55,-9.02c-0.2,0.03 -0.4,0.06 -0.69,0.11l4.34,3.17L14.99,1.92zM9.01,22.08C5.52,21.1 2.76,18.3 1.78,14.6L0.4,14.82c1.45,6.05 7.22,10.01 13.45,9.04c0.2,-0.03 0.4,-0.06 0.69,-0.11l-4.34,-3.17L9.01,22.08z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml
deleted file mode 100644
index c43e363c..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M11.72,7.61c-1.1,0.08,-1.93,1.08,-1.86,2.18l0.03,0.5l3.99,-0.27l-0.03,-0.5       C13.78,8.42,12.82,7.54,11.72,7.61z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M14.88,9.95l-0.03,-0.5c-0.11,-1.65 -1.54,-2.9 -3.2,-2.79C10.0,6.78 8.75,8.21 8.87,9.86l0.03,0.5c-0.55,0.04 -0.97,0.52 -0.93,1.07l0.34,4.99c0.04,0.55 0.52,0.97 1.07,0.93l5.99,-0.41c0.55,-0.04 0.97,-0.51 0.93,-1.07l-0.34,-4.99C15.91,10.33 15.43,9.91 14.88,9.95zM12.2,14.64c-0.55,0.04 -1.03,-0.38 -1.07,-0.93c-0.04,-0.55 0.38,-1.03 0.93,-1.07c0.55,-0.04 1.03,0.38 1.07,0.93C13.16,14.13 12.75,14.61 12.2,14.64zM13.88,10.02l-3.99,0.27l-0.03,-0.5c-0.08,-1.1 0.75,-2.11 1.86,-2.18c1.1,-0.08 2.06,0.81 2.13,1.91L13.88,10.02z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.71,2.17c3.41,1.23 5.96,4.23 6.67,7.98l1.5,-0.12c-1.1,-6.13 -6.58,-10.5 -12.86,-9.99c-0.2,0.02 -0.4,0.03 -0.7,0.06l4.1,3.48L15.71,2.17zM8.29,21.83c-3.41,-1.23 -5.96,-4.23 -6.67,-7.98l-1.4,0.11c1.0,6.14 6.48,10.51 12.76,9.99c0.2,-0.02 0.4,-0.03 0.7,-0.06l-4.1,-3.48L8.29,21.83z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml
deleted file mode 100644
index 22fa428..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="64dp"
-        android:height="64dp"/>
-
-    <viewport
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"/>
-
-    <path
-        android:pathData="M12.05,7.7c-1.1,0,-2,0.94,-2,2.05v0.5h4v-0.5C14.05,8.65,13.15,7.7,12.05,7.7z"
-        android:fill="#00000000"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M15.05,10.25l0.0,-0.5c0.0,-1.66 -1.34,-3.0 -3.0,-3.0s-2.99,1.34 -2.99,3.0l-0.01,0.5c-0.55,0.0 -1.0,0.45 -1.0,1.0l0.0,5.0c0.0,0.55 0.45,1.0 1.0,1.0l6.0,0.0c0.55,0.0 1.0,-0.45 1.0,-1.0l0.0,-5.0C16.05,10.7 15.6,10.25 15.05,10.25zM12.05,14.75c-0.55,0.0 -1.0,-0.45 -1.0,-1.0c0.0,-0.55 0.45,-1.0 1.0,-1.0s1.0,0.45 1.0,1.0C13.05,14.3 12.6,14.75 12.05,14.75zM14.05,10.25l-4.0,0.0l0.0,-0.5c0.0,-1.1 0.9,-2.05 2.0,-2.05s2.0,0.94 2.0,2.05L14.05,10.25z"/>
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M16.5,2.5c3.3,1.5 5.6,4.7 6.0,8.5L24.0,11.0C23.4,4.8 18.3,0.0 12.0,0.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.5,2.5zM7.5,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5L0.1,13.0C0.6,19.2 5.7,24.0 12.0,24.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.5,21.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml
new file mode 100644
index 0000000..e4c7cb5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="64dp"
+        android:height="64dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M2.0,14.0l0.0,20.0c0.0,2.2 1.8,4.0 4.0,4.0l36.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,14.0c0.0,-2.2 -1.8,-4.0 -4.0,-4.0L6.0,10.0C3.8,10.0 2.0,11.8 2.0,14.0zM38.0,14.0l0.0,20.0L10.0,34.0L10.0,14.0L38.0,14.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml
deleted file mode 100644
index 75e20f0..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml
+++ /dev/null
@@ -1,35 +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.
--->
-<animation-list
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:oneshot="true">
-    <item android:drawable="@drawable/ic_qs_rotation_01" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_02" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_03" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_04" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_05" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_06" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_07" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_08" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_09" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_10" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_11" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_12" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_13" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_14" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_15" android:duration="16" />
-</animation-list>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml
new file mode 100644
index 0000000..e4bf367
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="64dp"
+        android:height="64dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M34.0,2.0L14.0,2.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,36.0c0.0,2.2 1.8,4.0 4.0,4.0l20.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L38.0,6.0C38.0,3.8 36.2,2.0 34.0,2.0zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0L34.0,38.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
index a1cedb9..a6c2cf8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
 Copyright (C) 2014 The Android Open Source Project
 
@@ -14,22 +13,22 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<animation-list
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:oneshot="true">
-    <item android:drawable="@drawable/ic_qs_rotation_15" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_14" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_13" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_12" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_11" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_10" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_09" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_08" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_07" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_06" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_05" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_04" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_03" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_02" android:duration="16" />
-    <item android:drawable="@drawable/ic_qs_rotation_01" android:duration="16" />
-</animation-list>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="64dp"
+        android:height="64dp"/>
+
+    <viewport
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M10.25,1.75c-0.6,-0.6 -1.5,-0.6 -2.1,0.0l-6.4,6.4c-0.6,0.6 -0.6,1.5 0.0,2.1l12.0,12.0c0.6,0.6 1.5,0.6 2.1,0.0l6.4,-6.4c0.6,-0.6 0.6,-1.5 0.0,-2.1L10.25,1.75zM14.85,21.25l-12.0,-12.0l6.4,-6.4l12.0,12.0L14.85,21.25z"/>
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M16.55,2.5c3.3,1.5 5.6,4.7 6.0,8.5l1.5,0.0c-0.6,-6.2 -5.7,-11.0 -12.0,-11.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.55,2.5z"/>
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M7.55,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5l-1.4,0.0c0.5,6.2 5.6,11.0 11.9,11.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.55,21.5z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
new file mode 100644
index 0000000..1d6df61
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- 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"
+        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 816af57..0000000
--- a/packages/SystemUI/res/layout/user_switcher_host.xml
+++ /dev/null
@@ -1,37 +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"
-        android:background="#dd000000"
-        android:elevation="12dp">
-    <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/volume_panel_top"
-            android:background="@*android:drawable/dialog_full_holo_dark">
-        <ListView android:id="@android:id/list"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                tools:listitem="@layout/user_switcher_item"/>
-    </FrameLayout>
-</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 b79dbbe..751c889 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -516,6 +516,10 @@
     <string name="quick_settings_time_label">Time</string>
     <!-- QuickSettings: User [CHAR LIMIT=NONE] -->
     <string name="quick_settings_user_label">Me</string>
+    <!-- QuickSettings: Title of the user detail panel [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_user_title">User</string>
+    <!-- QuickSettings: Label on the item for adding a new user [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_user_new_user">New user</string>
     <!-- QuickSettings: Wifi [CHAR LIMIT=NONE] -->
     <string name="quick_settings_wifi_label">Wi-Fi</string>
     <!-- QuickSettings: Wifi (Not connected) [CHAR LIMIT=NONE] -->
@@ -687,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 449cc1d..4901f40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.QSTile.DetailAdapter;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSlider;
+import com.android.systemui.statusbar.phone.QSTileHost;
 
 import java.util.ArrayList;
 
@@ -61,9 +62,10 @@
     private boolean mExpanded;
     private boolean mListening;
 
-    private TileRecord mDetailRecord;
+    private Record mDetailRecord;
     private Callback mCallback;
     private BrightnessController mBrightnessController;
+    private QSTileHost mHost;
 
     public QSPanel(Context context) {
         this(context, null);
@@ -89,12 +91,27 @@
         mBrightnessController = new BrightnessController(getContext(),
                 (ImageView) findViewById(R.id.brightness_icon),
                 (ToggleSlider) findViewById(R.id.brightness_slider));
+
+        mDetailDoneButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showDetail(false, mDetailRecord);
+            }
+        });
     }
 
     public void setCallback(Callback callback) {
         mCallback = callback;
     }
 
+    public void setHost(QSTileHost host) {
+        mHost = host;
+    }
+
+    public QSTileHost getHost() {
+        return mHost;
+    }
+
     public void updateResources() {
         final Resources res = mContext.getResources();
         final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
@@ -108,6 +125,9 @@
             mColumns = columns;
             postInvalidate();
         }
+        if (mListening) {
+            refreshAllTiles();
+        }
     }
 
     public void setExpanded(boolean expanded) {
@@ -123,9 +143,9 @@
         mListening = listening;
         for (TileRecord r : mRecords) {
             r.tile.setListening(mListening);
-            if (mListening) {
-                r.tile.refreshState();
-            }
+        }
+        if (mListening) {
+            refreshAllTiles();
         }
         if (listening) {
             mBrightnessController.registerCallbacks();
@@ -134,7 +154,19 @@
         }
     }
 
-    private void showDetail(boolean show, TileRecord r) {
+    private void refreshAllTiles() {
+        for (TileRecord r : mRecords) {
+            r.tile.refreshState();
+        }
+    }
+
+    public void showDetailAdapter(boolean show, DetailAdapter adapter) {
+        Record r = new Record();
+        r.detailAdapter = adapter;
+        showDetail(show, r);
+    }
+
+    private void showDetail(boolean show, Record r) {
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
     }
 
@@ -194,40 +226,52 @@
         addView(r.tileView);
     }
 
-    private void handleShowDetail(TileRecord r, boolean show) {
-        if (r == null) return;
-        AnimatorListener listener = null;
+    private void handleShowDetail(Record r, boolean show) {
+        if (r instanceof TileRecord) {
+            handleShowDetailTile((TileRecord) r, show);
+        } else {
+            handleShowDetailImpl(r, show, getWidth() /* x */, 0/* y */);
+        }
+    }
+
+    private void handleShowDetailTile(TileRecord r, boolean show) {
+        if ((mDetailRecord != null) == show) return;
+
         if (show) {
-            if (mDetailRecord != null) return;  // already showing something in detail
             r.detailAdapter = r.tile.getDetailAdapter();
             if (r.detailAdapter == null) return;
-            mDetailRecord = r;
-            r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
+        }
+        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
+        int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
+        handleShowDetailImpl(r, show, x, y);
+    }
+
+    private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
+        if ((mDetailRecord != null) == show) return;  // already in right state
+        DetailAdapter detailAdapter = null;
+        AnimatorListener listener = null;
+        if (show) {
+            detailAdapter = r.detailAdapter;
+            r.detailView = detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
             if (r.detailView == null) throw new IllegalStateException("Must return detail view");
-            mDetailDoneButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    showDetail(false, mDetailRecord);
-                }
-            });
-            final Intent settingsIntent = r.detailAdapter.getSettingsIntent();
+
+            final Intent settingsIntent = detailAdapter.getSettingsIntent();
             mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
             mDetailSettingsButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
+                    mHost.startSettingsActivity(settingsIntent);
                 }
             });
+
             mDetailContent.removeAllViews();
             mDetail.bringToFront();
             mDetailContent.addView(r.detailView);
+            mDetailRecord = r;
         } else {
-            if (mDetailRecord == null) return;
             listener = mTeardownDetailWhenDone;
         }
-        fireShowingDetail(show ? r.detailAdapter : null);
-        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
-        int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
+        fireShowingDetail(show ? detailAdapter : null);
         mClipper.animateCircularClip(x, y, show, listener);
     }
 
@@ -330,18 +374,21 @@
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == SHOW_DETAIL) {
-                handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
+                handleShowDetail((Record)msg.obj, msg.arg1 != 0);
             } else if (msg.what == SET_TILE_VISIBILITY) {
                 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
             }
         }
     }
 
-    private static final class TileRecord {
-        QSTile<?> tile;
-        QSTileView tileView;
+    private static class Record {
         View detailView;
         DetailAdapter detailAdapter;
+    }
+
+    private static final class TileRecord extends Record {
+        QSTile<?> tile;
+        QSTileView tileView;
         int row;
         int col;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 3be97cc..21cf9ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -18,7 +18,6 @@
 
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.drawable.AnimationDrawable;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
@@ -61,35 +60,19 @@
         final boolean rotationLocked = mController.isRotationLocked();
         state.visible = mController.isRotationLockAffordanceVisible();
         final Resources res = mContext.getResources();
-        if (state.value != rotationLocked) {
-            state.value = rotationLocked;
-            final AnimationDrawable d = (AnimationDrawable) res.getDrawable(rotationLocked
-                    ? R.drawable.ic_qs_rotation_locked
-                    : R.drawable.ic_qs_rotation_unlocked);
-            state.icon = d;
-            mUiHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    d.start();
-                }
-            });
-        }
+        state.value = rotationLocked;
         if (rotationLocked) {
-            final int lockOrientation = mController.getRotationLockOrientation();
-            final int label = lockOrientation == Configuration.ORIENTATION_PORTRAIT
-                    ? R.string.quick_settings_rotation_locked_portrait_label
-                    : lockOrientation == Configuration.ORIENTATION_LANDSCAPE
-                    ? R.string.quick_settings_rotation_locked_landscape_label
-                    : R.string.quick_settings_rotation_locked_label;
+            final boolean portrait = res.getConfiguration().orientation
+                    != Configuration.ORIENTATION_LANDSCAPE;
+            final int label = portrait ? R.string.quick_settings_rotation_locked_portrait_label
+                    : R.string.quick_settings_rotation_locked_landscape_label;
+            final int icon = portrait ? R.drawable.ic_qs_rotation_portrait
+                    : R.drawable.ic_qs_rotation_landscape;
             state.label = mContext.getString(label);
-            if (state.icon == null) {
-                state.icon = res.getDrawable(R.drawable.ic_qs_rotation_15);
-            }
+            state.icon = mContext.getDrawable(icon);
         } else {
             state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
-            if (state.icon == null) {
-                state.icon = res.getDrawable(R.drawable.ic_qs_rotation_01);
-            }
+            state.icon = res.getDrawable(R.drawable.ic_qs_rotation_unlocked);
         }
     }
 
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/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index a9a606f..b6d7d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -64,11 +64,9 @@
 
     Context mContext;
     SystemServicesProxy mSystemServicesProxy;
-
-    // Recents service binding
     Handler mHandler;
-    boolean mBootCompleted = false;
-    boolean mStartAnimationTriggered = false;
+    boolean mBootCompleted;
+    boolean mStartAnimationTriggered;
 
     // Task launching
     RecentsConfiguration mConfig;
@@ -95,9 +93,7 @@
     }
 
     public void onStart() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|start]");
-        }
+        // Do nothing
     }
 
     public void onBootCompleted() {
@@ -106,9 +102,6 @@
 
     /** Shows the recents */
     public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|showRecents]");
-        }
         mStatusBarView = statusBarView;
         mTriggeredFromAltTab = triggeredFromAltTab;
 
@@ -121,10 +114,6 @@
 
     /** Hides the recents */
     public void onHideRecents(boolean triggeredFromAltTab) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]");
-        }
-
         if (mBootCompleted) {
             if (isRecentsTopMost(getTopMostTask(), null)) {
                 // Notify recents to hide itself
@@ -139,13 +128,6 @@
 
     /** Toggles the alternate recents activity */
     public void onToggleRecents(View statusBarView) {
-        if (Console.Enabled) {
-            Console.logStartTracingTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey);
-            Console.logStartTracingTime(Constants.Log.App.TimeRecentsLaunchTask,
-                    Constants.Log.App.TimeRecentsLaunchKey);
-            Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|toggleRecents]", "");
-        }
         mStatusBarView = statusBarView;
         mTriggeredFromAltTab = false;
 
@@ -223,14 +205,6 @@
             intent.setPackage(mContext.getPackageName());
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             mContext.sendBroadcast(intent);
-
-            // Time this path
-            if (Console.Enabled) {
-                Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                        Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
-                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                        Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
-            }
             mLastToggleTime = System.currentTimeMillis();
             return;
         } else {
@@ -395,11 +369,6 @@
                 startAlternateRecentsActivity(topTask, opts, null);
             }
         }
-
-        if (Console.Enabled) {
-            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity");
-        }
         mLastToggleTime = System.currentTimeMillis();
     }
 
@@ -417,10 +386,9 @@
         intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
         intent.putExtra(EXTRA_TRIGGERED_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
         if (opts != null) {
-            mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
-                    UserHandle.USER_CURRENT));
+            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
         } else {
-            mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index e7ac2e1..c49e244 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -52,43 +52,6 @@
         }
     }
 
-    public static class Log {
-        public static class App {
-            public static final String TimeRecentsStartupKey = "startup";
-            public static final String TimeRecentsLaunchKey = "launchTask";
-            public static final String TimeRecentsScreenshotTransitionKey = "screenshot";
-            public static final boolean TimeRecentsStartup = false;
-            public static final boolean TimeRecentsLaunchTask = false;
-            public static final boolean TimeRecentsScreenshotTransition = false;
-
-
-            public static final boolean RecentsComponent = false;
-            public static final boolean TaskDataLoader = false;
-            public static final boolean SystemUIHandshake = false;
-            public static final boolean TimeSystemCalls = false;
-            public static final boolean Memory = false;
-            public static final boolean Search = false;
-        }
-
-        public static class UI {
-            public static final boolean Draw = false;
-            public static final boolean ClickEvents = false;
-            public static final boolean TouchEvents = false;
-            public static final boolean MeasureAndLayout = false;
-            public static final boolean HwLayers = false;
-            public static final boolean Focus = false;
-        }
-
-        public static class TaskStack {
-            public static final boolean SynchronizeViewsWithModel = false;
-        }
-
-        public static class ViewPool {
-            public static final boolean PoolCallbacks = false;
-        }
-    }
-
-    /** XXX: We are going to move almost all of these into a resource once they are nailed down. */
     public static class Values {
         public static class App {
             public static int AppWidgetHostId = 1024;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 1e581c1..29a0262 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -52,16 +52,23 @@
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 
-/* Activity */
+/**
+ * The main Recents activity that is started from AlternateRecentsComponent.
+ */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
         RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
         FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
 
+    // Actions and Extras sent from AlternateRecentsComponent
     final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
     final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
     final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
     final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
 
+    RecentsConfiguration mConfig;
+    boolean mVisible;
+
+    // Top level views
     RecentsView mRecentsView;
     SystemBarScrimViews mScrimViews;
     ViewStub mEmptyViewStub;
@@ -69,29 +76,29 @@
     ViewStub mFullscreenOverlayStub;
     FullscreenTransitionOverlayView mFullScreenOverlayView;
 
-    RecentsConfiguration mConfig;
-
+    // Search AppWidget
     RecentsAppWidgetHost mAppWidgetHost;
     AppWidgetProviderInfo mSearchAppWidgetInfo;
     AppWidgetHostView mSearchAppWidgetHostView;
 
-    boolean mVisible;
 
     // Runnables to finish the Recents activity
-    FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
+    FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable();
     FinishRecentsRunnable mFinishLaunchHomeRunnable;
 
     /**
-     * A Runnable to finish Recents either with/without a transition, and either by calling finish()
-     * or just launching the specified intent.
+     * A common Runnable to finish Recents either by calling finish() (with a custom animation) or
+     * launching Home with some ActivityOptions.  Generally we always launch home when we exit
+     * Recents rather than just finishing the activity since we don't know what is behind Recents in
+     * the task stack.  The only case where we finish() directly is when we are cancelling the full
+     * screen transition from the app.
      */
     class FinishRecentsRunnable implements Runnable {
-        boolean mUseCustomFinishTransition;
         Intent mLaunchIntent;
         ActivityOptions mLaunchOpts;
 
-        public FinishRecentsRunnable(boolean withTransition) {
-            mUseCustomFinishTransition = withTransition;
+        public FinishRecentsRunnable() {
+            // Do nothing
         }
 
         /**
@@ -111,77 +118,66 @@
             // Finish Recents
             if (mLaunchIntent != null) {
                 if (mLaunchOpts != null) {
-                    startActivityAsUser(mLaunchIntent, new UserHandle(UserHandle.USER_CURRENT));
+                    startActivityAsUser(mLaunchIntent, UserHandle.CURRENT);
                 } else {
-                    startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(),
-                            new UserHandle(UserHandle.USER_CURRENT));
+                    startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
                 }
             } else {
                 finish();
-                if (mUseCustomFinishTransition) {
-                    overridePendingTransition(R.anim.recents_to_launcher_enter,
-                            R.anim.recents_to_launcher_exit);
-                }
+                overridePendingTransition(R.anim.recents_to_launcher_enter,
+                        R.anim.recents_to_launcher_exit);
             }
         }
     }
 
-    // Broadcast receiver to handle messages from AlternateRecentsComponent
+    /**
+     * Broadcast receiver to handle messages from AlternateRecentsComponent.
+     */
     final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (Console.Enabled) {
-                Console.log(Constants.Log.App.SystemUIHandshake,
-                        "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
-            }
             if (action.equals(ACTION_HIDE_RECENTS_ACTIVITY)) {
                 if (intent.getBooleanExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
-                    // Dismiss recents, launching the focused task
-                    dismissRecentsIfVisible();
+                    // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
+                    dismissRecentsToFocusedTaskOrHome(false);
                 } else {
-                    // If we are mid-animation into Recents, then reverse it and finish
-                    if (mFullScreenOverlayView == null ||
-                            !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
-                        // Otherwise, either finish Recents, or launch Home directly
-                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
-                                null, mFinishLaunchHomeRunnable, null);
-                        mRecentsView.startExitToHomeAnimation(
-                                new ViewAnimation.TaskViewExitContext(exitTrigger));
-                    }
+                    // Otherwise, dismiss Recents to Home
+                    dismissRecentsToHome(true);
                 }
             } else if (action.equals(ACTION_TOGGLE_RECENTS_ACTIVITY)) {
-                // Try and unfilter and filtered stacks
-                if (!mRecentsView.unfilterFilteredStacks()) {
-                    // If there are no filtered stacks, dismiss recents and launch the first task
-                    dismissRecentsIfVisible();
-                }
+                // If we are toggling Recents, then first unfilter any filtered stacks first
+                dismissRecentsToFocusedTaskOrHome(true);
             } else if (action.equals(ACTION_START_ENTER_ANIMATION)) {
                 // Try and start the enter animation (or restart it on configuration changed)
                 ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
                 mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
                         mFullScreenOverlayView, t));
-                // Call our callback
                 onEnterAnimationTriggered();
             }
         }
     };
 
-    // Broadcast receiver to handle messages from the system
+    /**
+     * Broadcast receiver to handle messages from the system
+     */
     final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(Intent.ACTION_SCREEN_OFF) && mVisible) {
-                mFinishLaunchHomeRunnable.run();
+            if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                // When the screen turns off, dismiss Recents to Home
+                dismissRecentsToHome(false);
             } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
-                // Refresh the search widget
+                // When the search activity changes, update the Search widget
                 refreshSearchWidget();
             }
         }
     };
 
-    // Debug trigger
+    /**
+     * A custom debug trigger to listen for a debug key chord.
+     */
     final DebugTrigger mDebugTrigger = new DebugTrigger(new Runnable() {
         @Override
         public void run() {
@@ -211,15 +207,17 @@
         mConfig.launchedToTaskId = launchIntent.getIntExtra(
                 AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_TASK_ID, -1);
 
-        // Add the default no-recents layout
-        if (mEmptyView == null) {
-            mEmptyView = mEmptyViewStub.inflate();
-        }
+        // Update the top level view's visibilities
         if (mConfig.launchedWithNoRecentTasks) {
+            if (mEmptyView == null) {
+                mEmptyView = mEmptyViewStub.inflate();
+            }
             mEmptyView.setVisibility(View.VISIBLE);
             mRecentsView.setSearchBarVisibility(View.GONE);
         } else {
-            mEmptyView.setVisibility(View.GONE);
+            if (mEmptyView != null) {
+                mEmptyView.setVisibility(View.GONE);
+            }
             if (mRecentsView.hasSearchBar()) {
                 mRecentsView.setSearchBarVisibility(View.VISIBLE);
             } else {
@@ -227,7 +225,7 @@
             }
         }
 
-        // Show the scrim if we animate into Recents without window transitions
+        // Animate the SystemUI scrims into view
         mScrimViews.prepareEnterRecentsAnimation();
     }
 
@@ -250,12 +248,6 @@
                     ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
                     appWidgetId = -1;
                 }
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.SystemUIHandshake,
-                            "[RecentsActivity|onCreate|settings|appWidgetId]",
-                            "Id: " + appWidgetId,
-                            Console.AnsiBlue);
-                }
             }
 
             // If there is no id, then bind a new search app widget
@@ -263,13 +255,6 @@
                 Pair<Integer, AppWidgetProviderInfo> widgetInfo =
                         ssp.bindSearchAppWidget(mAppWidgetHost);
                 if (widgetInfo != null) {
-                    if (Console.Enabled) {
-                        Console.log(Constants.Log.App.SystemUIHandshake,
-                                "[RecentsActivity|onCreate|searchWidget]",
-                                "Id: " + widgetInfo.first + " Info: " + widgetInfo.second,
-                                Console.AnsiBlue);
-                    }
-
                     // Save the app widget id into the settings
                     mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
                     mSearchAppWidgetInfo = widgetInfo.second;
@@ -283,12 +268,6 @@
         if (Constants.DebugFlags.App.EnableSearchLayout) {
             int appWidgetId = mConfig.searchBarAppWidgetId;
             if (appWidgetId >= 0) {
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.SystemUIHandshake,
-                            "[RecentsActivity|onCreate|addSearchAppWidgetView]",
-                            "Id: " + appWidgetId,
-                            Console.AnsiBlue);
-                }
                 mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
                         mSearchAppWidgetInfo);
                 Bundle opts = new Bundle();
@@ -305,28 +284,50 @@
     }
 
     /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
-    boolean dismissRecentsIfVisible() {
+    boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
         if (mVisible) {
-            // If we are mid-animation into Recents, then reverse it and finish
-            if (mFullScreenOverlayView == null ||
-                    !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
-                // If we have a focused task, then launch that task
-                if (!mRecentsView.launchFocusedTask()) {
-                    if (mConfig.launchedFromHome) {
-                        // Just start the animation out of recents
-                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
-                                null, mFinishLaunchHomeRunnable, null);
-                        mRecentsView.startExitToHomeAnimation(
-                                new ViewAnimation.TaskViewExitContext(exitTrigger));
-                    } else {
-                        // Otherwise, try and launch the first task
-                        if (!mRecentsView.launchFirstTask()) {
-                            // If there are no tasks, then just finish recents
-                            mFinishLaunchHomeRunnable.run();
-                        }
-                    }
-                }
+            // If we are mid-animation into Recents, reverse the animation now
+            if (mFullScreenOverlayView != null &&
+                mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
+            // If we currently have filtered stacks, then unfilter those first
+            if (checkFilteredStackState &&
+                mRecentsView.unfilterFilteredStacks()) return true;
+            // If we have a focused Task, launch that Task now
+            if (mRecentsView.launchFocusedTask()) return true;
+            // If we launched from Home, then return to Home
+            if (mConfig.launchedFromHome) {
+                dismissRecentsToHomeRaw(true);
+                return true;
             }
+            // Otherwise, try and return to the first Task in the stack
+            if (mRecentsView.launchFirstTask()) return true;
+            // If none of the other cases apply, then just go Home
+            dismissRecentsToHomeRaw(true);
+            return true;
+        }
+        return false;
+    }
+
+    /** Dismisses Recents directly to Home. */
+    void dismissRecentsToHomeRaw(boolean animated) {
+        if (animated) {
+            ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                    null, mFinishLaunchHomeRunnable, null);
+            mRecentsView.startExitToHomeAnimation(
+                    new ViewAnimation.TaskViewExitContext(exitTrigger));
+        } else {
+            mFinishLaunchHomeRunnable.run();
+        }
+    }
+
+    /** Dismisses Recents directly to Home if we currently aren't transitioning. */
+    boolean dismissRecentsToHome(boolean animated) {
+        if (mVisible) {
+            // If we are mid-animation into Recents, reverse the animation now
+            if (mFullScreenOverlayView != null &&
+                mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
+            // Return to Home
+            dismissRecentsToHomeRaw(animated);
             return true;
         }
         return false;
@@ -336,13 +337,6 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (Console.Enabled) {
-            Console.logDivider(Constants.Log.App.SystemUIHandshake);
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onCreate]",
-                    getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed);
-            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey, "onCreate");
-        }
 
         // Initialize the loader and the configuration
         RecentsTaskLoader.initialize(this);
@@ -410,11 +404,13 @@
     }
 
     void onConfigurationChange() {
+        // Update RecentsConfiguration
+        mConfig = RecentsConfiguration.reinitialize(this);
+
         // Try and start the enter animation (or restart it on configuration changed)
         ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
         mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
                 mFullScreenOverlayView, t));
-        // Call our callback
         onEnterAnimationTriggered();
     }
 
@@ -423,18 +419,6 @@
         super.onNewIntent(intent);
         setIntent(intent);
 
-        if (Console.Enabled) {
-            Console.logDivider(Constants.Log.App.SystemUIHandshake);
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]",
-                    intent.getAction() + " visible: " + mVisible, Console.AnsiRed);
-            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey, "onNewIntent");
-        }
-
-        // Initialize the loader and the configuration
-        RecentsTaskLoader.initialize(this);
-        mConfig = RecentsConfiguration.reinitialize(this);
-
         // Update the recent tasks
         updateRecentsTasks(intent);
 
@@ -446,10 +430,6 @@
 
     @Override
     protected void onStart() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
-                    Console.AnsiRed);
-        }
         super.onStart();
 
         // Register the broadcast receiver to handle messages from our service
@@ -462,10 +442,6 @@
 
     @Override
     protected void onResume() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onResume]", "",
-                    Console.AnsiRed);
-        }
         super.onResume();
 
         // Start listening for widget package changes if there is one bound, post it since we don't
@@ -484,64 +460,28 @@
             }, 1);
         }
 
+        // Mark Recents as visible
         mVisible = true;
     }
 
     @Override
-    public void onAttachedToWindow() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake,
-                    "[RecentsActivity|onAttachedToWindow]", "",
-                    Console.AnsiRed);
-        }
-        super.onAttachedToWindow();
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake,
-                    "[RecentsActivity|onDetachedFromWindow]", "",
-                    Console.AnsiRed);
-        }
-        super.onDetachedFromWindow();
-    }
-
-    @Override
-    protected void onPause() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onPause]", "",
-                    Console.AnsiRed);
-        }
-        super.onPause();
-    }
-
-    @Override
     protected void onStop() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStop]", "",
-                    Console.AnsiRed);
-        }
         super.onStop();
 
         // Unregister the RecentsService receiver
         unregisterReceiver(mServiceBroadcastReceiver);
 
         // Stop listening for widget package changes if there was one bound
-        if (mConfig.searchBarAppWidgetId >= 0) {
+        if (mAppWidgetHost.isListening()) {
             mAppWidgetHost.stopListening();
         }
     }
 
     @Override
     protected void onDestroy() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onDestroy]", "",
-                    Console.AnsiRed);
-        }
         super.onDestroy();
 
-        // Unregister the screen off receiver
+        // Unregister the system broadcast receivers
         unregisterReceiver(mSystemBroadcastReceiver);
         RecentsTaskLoader.getInstance().unregisterReceivers();
     }
@@ -583,26 +523,8 @@
         // Test mode where back does not do anything
         if (mConfig.debugModeEnabled) return;
 
-        // If we are mid-animation into Recents, then reverse it and finish
-        if (mFullScreenOverlayView == null ||
-                !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
-            // If we are currently filtering in any stacks, unfilter them first
-            if (!mRecentsView.unfilterFilteredStacks()) {
-                if (mConfig.launchedFromHome) {
-                    // Just start the animation out of recents
-                    ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
-                            null, mFinishLaunchHomeRunnable, null);
-                    mRecentsView.startExitToHomeAnimation(
-                            new ViewAnimation.TaskViewExitContext(exitTrigger));
-                } else {
-                    // Otherwise, try and launch the first task
-                    if (!mRecentsView.launchFirstTask()) {
-                        // If there are no tasks, then just finish recents
-                        mFinishLaunchHomeRunnable.run();
-                    }
-                }
-            }
-        }
+        // Dismiss Recents to the focused Task or Home
+        dismissRecentsToFocusedTaskOrHome(true);
     }
 
     /** Called when debug mode is triggered */
@@ -623,7 +545,7 @@
 
     /** Called when the enter recents animation is triggered. */
     public void onEnterAnimationTriggered() {
-        // Animate the scrims in
+        // Animate the SystemUI scrim views
         mScrimViews.startEnterRecentsAnimation();
     }
 
@@ -644,7 +566,7 @@
 
     @Override
     public void onExitToHomeAnimationTriggered() {
-        // Animate the scrims out
+        // Animate the SystemUI scrim views out
         mScrimViews.startExitRecentsAnimation();
     }
 
@@ -664,7 +586,6 @@
 
     @Override
     public void refreshSearchWidget() {
-        // Load the Search widget again
         bindSearchBarAppWidget();
         addSearchBarAppWidgetView();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 43d7a54..a63e167 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -33,6 +33,7 @@
     Context mContext;
     RecentsAppWidgetHostCallbacks mCb;
     RecentsConfiguration mConfig;
+    boolean mIsListening;
 
     public RecentsAppWidgetHost(Context context, int hostId) {
         super(context, hostId);
@@ -42,6 +43,7 @@
 
     public void startListening(RecentsAppWidgetHostCallbacks cb) {
         mCb = cb;
+        mIsListening = true;
         super.startListening();
     }
 
@@ -51,6 +53,11 @@
         // Ensure that we release any references to the callbacks
         mCb = null;
         mContext = null;
+        mIsListening = false;
+    }
+
+    public boolean isListening() {
+        return mIsListening;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index e62d989..439765e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -35,6 +35,7 @@
  * NOTE: We should not hold any references to a Context from a static instance */
 public class RecentsConfiguration {
     static RecentsConfiguration sInstance;
+    static int sPrevConfigurationHashCode;
 
     DisplayMetrics mDisplayMetrics;
 
@@ -138,7 +139,11 @@
         if (sInstance == null) {
             sInstance = new RecentsConfiguration(context);
         }
-        sInstance.update(context);
+        int configHashCode = context.getResources().getConfiguration().hashCode();
+        if (sPrevConfigurationHashCode != configHashCode) {
+            sInstance.update(context);
+            sPrevConfigurationHashCode = configHashCode;
+        }
         return sInstance;
     }
 
@@ -179,10 +184,8 @@
         transposeRecentsLayoutWithOrientation =
                 res.getBoolean(R.bool.recents_transpose_layout_with_orientation);
 
-        // Search bar
+        // Search Bar
         searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
-
-        // Update the search widget id
         searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
 
         // Task stack
@@ -242,12 +245,6 @@
         // Nav bar scrim
         navBarScrimEnterDuration =
                 res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
-
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout,
-                    "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
-                    Console.AnsiGreen);
-        }
     }
 
     /** Updates the system insets */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index b8beda6f..ced4043 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -31,6 +31,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
@@ -80,6 +81,8 @@
     ComponentName mAssistComponent;
 
     Bitmap mDummyIcon;
+    int mDummyThumbnailWidth;
+    int mDummyThumbnailHeight;
     Paint mBgProtectionPaint;
     Canvas mBgProtectionCanvas;
 
@@ -96,6 +99,13 @@
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
 
+        // Get the dummy thumbnail width/heights
+        Resources res = context.getResources();
+        int wId = com.android.internal.R.dimen.thumbnail_width;
+        int hId = com.android.internal.R.dimen.thumbnail_height;
+        mDummyThumbnailWidth = res.getDimensionPixelSize(wId);
+        mDummyThumbnailHeight = res.getDimensionPixelSize(hId);
+
         // Create the protection paints
         mBgProtectionPaint = new Paint();
         mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
@@ -213,7 +223,8 @@
 
         // If we are mocking, then just return a dummy thumbnail
         if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
-            Bitmap thumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
+                    Bitmap.Config.ARGB_8888);
             thumbnail.eraseColor(0xff333333);
             return thumbnail;
         }
@@ -239,6 +250,8 @@
      */
     public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
         ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+        if (taskThumbnail == null) return null;
+
         Bitmap thumbnail = taskThumbnail.mainThumbnail;
         ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
         if (thumbnail == null && descriptor != null) {
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/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index cbb8892..71979c4f7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -29,7 +29,6 @@
 import android.os.UserHandle;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import java.util.Collections;
@@ -44,9 +43,6 @@
 
     /** Adds a new task to the load queue */
     void addTask(Task t) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|addTask]");
-        }
         if (!mQueue.contains(t)) {
             mQueue.add(t);
         }
@@ -60,25 +56,16 @@
      * force reloaded.
      */
     Task nextTask() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|nextTask]");
-        }
         return mQueue.poll();
     }
 
     /** Removes a task from the load queue */
     void removeTask(Task t) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|removeTask]");
-        }
         mQueue.remove(t);
     }
 
     /** Clears all the tasks from the load queue */
     void clearTasks() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|clearTasks]");
-        }
         mQueue.clear();
     }
 
@@ -124,9 +111,6 @@
 
     /** Restarts the loader thread */
     void start(Context context) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|start]");
-        }
         mContext = context;
         mCancelled = false;
         mSystemServicesProxy = new SystemServicesProxy(context);
@@ -138,9 +122,6 @@
 
     /** Requests the loader thread to stop after the current iteration */
     void stop() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|stop]");
-        }
         // Mark as cancelled for the thread to pick up
         mCancelled = true;
         mSystemServicesProxy = null;
@@ -154,25 +135,13 @@
     @Override
     public void run() {
         while (true) {
-            if (Console.Enabled) {
-                Console.log(Constants.Log.App.TaskDataLoader,
-                        "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
-            }
             if (mCancelled) {
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.TaskDataLoader,
-                            "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
-                }
                 // We have to unset the context here, since the background thread may be using it
                 // when we call stop()
                 mContext = null;
                 // If we are cancelled, then wait until we are started again
                 synchronized(mLoadThread) {
                     try {
-                        if (Console.Enabled) {
-                            Console.log(Constants.Log.App.TaskDataLoader,
-                                    "[TaskResourceLoader|waitOnLoadThreadCancelled]");
-                        }
                         mLoadThread.wait();
                     } catch (InterruptedException ie) {
                         ie.printStackTrace();
@@ -186,11 +155,6 @@
                 if (t != null) {
                     Drawable cachedIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
                     Bitmap cachedThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
-                    if (Console.Enabled) {
-                        Console.log(Constants.Log.App.TaskDataLoader,
-                                "  [TaskResourceLoader|load]",
-                                t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail);
-                    }
                     // Load the application icon if it is stale or we haven't cached one yet
                     if (cachedIcon == null) {
                         Drawable icon = null;
@@ -198,10 +162,6 @@
                                 t.userId);
                         if (info != null) {
                             icon = ssp.getActivityIcon(info, t.userId);
-                            if (Console.Enabled) {
-                                Console.log(Constants.Log.App.TaskDataLoader,
-                                        "    [TaskResourceLoader|loadedIcon]", icon);
-                            }
                         }
                         // If we can't load the icon, then set the default application icon into the
                         // cache.  This will remain until the task's last active time is updated.
@@ -213,10 +173,6 @@
                         Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
                         if (thumbnail != null) {
                             thumbnail.setHasAlpha(false);
-                            if (Console.Enabled) {
-                                Console.log(Constants.Log.App.TaskDataLoader,
-                                        "    [TaskResourceLoader|loadedThumbnail]", thumbnail);
-                            }
                         }
                         // Even if we can't load the icon, we set the default thumbnail into the
                         // cache.  This will remain until the task's last active time is updated.
@@ -240,10 +196,6 @@
                 if (!mCancelled && mLoadQueue.isEmpty()) {
                     synchronized(mLoadQueue) {
                         try {
-                            if (Console.Enabled) {
-                                Console.log(Constants.Log.App.TaskDataLoader,
-                                        "[TaskResourceLoader|waitOnLoadQueue]");
-                            }
                             mWaitingOnLoadQueue = true;
                             mLoadQueue.wait();
                             mWaitingOnLoadQueue = false;
@@ -290,12 +242,6 @@
         int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxThumbnailCacheSize;
 
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
-                    " iconCache: " + iconCacheSize);
-        }
-
         // Create the default assets
         Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
         icon.eraseColor(0x00000000);
@@ -315,13 +261,6 @@
         mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
         mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
                 mDefaultThumbnail, mDefaultApplicationIcon);
-
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|defaultBitmaps]",
-                    "icon: " + mDefaultApplicationIcon +
-                    " default thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
-        }
     }
 
     /** Initializes the recents task loader */
@@ -343,29 +282,15 @@
     }
 
     private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp) {
-        long t1 = System.currentTimeMillis();
-
         List<ActivityManager.RecentTaskInfo> tasks =
                 ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier());
         Collections.reverse(tasks);
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TimeSystemCalls,
-                    "[RecentsTaskLoader|getRecentTasks]",
-                    "" + (System.currentTimeMillis() - t1) + "ms");
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|tasks]", "" + tasks.size());
-        }
 
         return tasks;
     }
 
     /** Reload the set of recent tasks */
     public SpaceNode reload(Context context, int preloadCount) {
-        long t1 = System.currentTimeMillis();
-
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
-        }
         RecentsConfiguration config = RecentsConfiguration.getInstance();
         Resources res = context.getResources();
         LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>();
@@ -378,7 +303,6 @@
         List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
 
         // Add each task to the task stack
-        t1 = System.currentTimeMillis();
         int taskCount = tasks.size();
         for (int i = 0; i < taskCount; i++) {
             ActivityManager.RecentTaskInfo t = tasks.get(i);
@@ -408,12 +332,6 @@
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.TaskDataLoader,
-                            "[RecentsTaskLoader|preloadTask]",
-                            "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
-                }
-
                 // Load the icon from the cache if possible
                 task.applicationIcon = mApplicationIconCache.getCheckLastActiveTime(task.key);
                 if (task.applicationIcon == null) {
@@ -436,10 +354,6 @@
                 // Load the thumbnail (if possible and not the foremost task, from the cache)
                 task.thumbnail = mThumbnailCache.getCheckLastActiveTime(task.key);
                 if (task.thumbnail == null) {
-                    if (Console.Enabled) {
-                        Console.log(Constants.Log.App.TaskDataLoader,
-                                "[RecentsTaskLoader|loadingTaskThumbnail]");
-                    }
                     if (isForemostTask) {
                         // We force loading the thumbnail icon for the foremost task
                         task.thumbnail = ssp.getTaskThumbnail(task.key.id);
@@ -460,17 +374,8 @@
             }
 
             // Add the task to the stack
-            if (Console.Enabled) {
-                Console.log(Constants.Log.App.TaskDataLoader,
-                        "  [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
-            }
             stack.addTask(task);
         }
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TimeSystemCalls,
-                    "[RecentsTaskLoader|getAllTaskTopThumbnail]",
-                    "" + (System.currentTimeMillis() - t1) + "ms");
-        }
 
         // Simulate the groupings that we describe
         stack.createAffiliatedGroupings();
@@ -512,12 +417,6 @@
         Drawable applicationIcon = mApplicationIconCache.get(t.key);
         Bitmap thumbnail = mThumbnailCache.get(t.key);
 
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
-                    t + " applicationIcon: " + applicationIcon + " thumbnail: " + thumbnail +
-                            " thumbnailCacheSize: " + mThumbnailCache.size());
-        }
-
         boolean requiresLoad = false;
         if (applicationIcon == null) {
             applicationIcon = mDefaultApplicationIcon;
@@ -535,23 +434,12 @@
 
     /** Releases the task resource data back into the pool. */
     public void unloadTaskData(Task t) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|unloadTask]", t +
-                    " thumbnailCacheSize: " + mThumbnailCache.size());
-        }
-
         mLoadQueue.removeTask(t);
         t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
     }
 
     /** Completely removes the resource data from the pool. */
     public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|deleteTask]", t);
-        }
-
         mLoadQueue.removeTask(t);
         mThumbnailCache.remove(t.key);
         mApplicationIconCache.remove(t.key);
@@ -562,9 +450,6 @@
 
     /** Stops the task loader and clears all pending tasks */
     void stopLoader() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
-        }
         mLoader.stop();
         mLoadQueue.clearTasks();
     }
@@ -585,11 +470,6 @@
      * out of memory.
      */
     public void onTrimMemory(int level) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
-                    Console.trimMemoryLevelToString(level));
-        }
-
         switch (level) {
             case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
                 // Stop the loader immediately when the UI is no longer visible
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/FullscreenTransitionOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
index 57b8ea4..63f59be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
@@ -22,8 +22,6 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -34,9 +32,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.Console;
 
 
 /**
@@ -152,11 +148,6 @@
     public void prepareAnimateOnEnterRecents(Bitmap screenshot) {
         if (!mConfig.launchedFromAppWithScreenshot) return;
 
-        if (Console.Enabled) {
-            Console.logStartTracingTime(Constants.Log.App.TimeRecentsScreenshotTransition,
-                    Constants.Log.App.TimeRecentsScreenshotTransitionKey);
-        }
-
         setClipTop(0);
         setClipBottom(getMeasuredHeight());
         setDim(0);
@@ -180,11 +171,6 @@
     /** Animates this view as it enters recents */
     public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx,
                                       final Runnable postAnimRunnable) {
-        if (Console.Enabled) {
-            Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
-                    Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Starting");
-        }
-
         // Cancel the current animation
         if (mEnterAnimation != null) {
             mEnterAnimation.removeAllListeners();
@@ -226,11 +212,6 @@
                         mCb.onEnterAnimationComplete();
                         // Run the given post-anim runnable
                         postAnimRunnable.run();
-
-                        if (Console.Enabled) {
-                            Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
-                                    Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Completed");
-                        }
                     }
                 });
             }
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 73bbf86..b32d3dd 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
@@ -141,20 +141,12 @@
                     TaskView tv = (TaskView) stackView.getChildAt(j);
                     Task task = tv.getTask();
                     if (tv.isFocusedTask()) {
-                        if (Console.Enabled) {
-                            Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
-                                    "Found focused Task");
-                        }
                         onTaskViewClicked(stackView, tv, stack, task, false);
                         return true;
                     }
                 }
             }
         }
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
-                    "No Tasks focused");
-        }
         return false;
     }
 
@@ -228,12 +220,6 @@
             if (searchBar != null) {
                 mSearchBar = searchBar;
                 addView(mSearchBar);
-
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsView|setSearchBar]",
-                            "" + (mSearchBar.getVisibility() == View.VISIBLE),
-                            Console.AnsiBlue);
-                }
             }
         }
     }
@@ -260,13 +246,6 @@
         int height = MeasureSpec.getSize(heightMeasureSpec);
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|measure]",
-                    "width: " + width + " height: " + height, Console.AnsiGreen);
-            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onMeasure");
-        }
-
         // Get the search bar bounds and measure the search bar layout
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
@@ -303,13 +282,6 @@
      */
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|layout]",
-                    new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
-            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onLayout");
-        }
-
         // Get the search bar bounds so that we lay it out
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
@@ -351,11 +323,6 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout,
-                    "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
-        }
-
         // Update the configuration with the latest system insets and trigger a relayout
         mConfig.updateSystemInsets(insets.getSystemWindowInsets());
         requestLayout();
@@ -482,11 +449,6 @@
         final Runnable launchRunnable = new Runnable() {
             @Override
             public void run() {
-                if (Console.Enabled) {
-                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                            Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity");
-                }
-
                 if (task.isActive) {
                     // Bring an active task to the foreground
                     RecentsTaskLoader.getInstance().getSystemServicesProxy()
@@ -515,19 +477,9 @@
                     // And clean up the old task
                     onTaskViewDismissed(task);
                 }
-
-                if (Console.Enabled) {
-                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                            Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
-                }
             }
         };
 
-        if (Console.Enabled) {
-            Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                    Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
-        }
-
         // Launch the app right away if there is no task view, otherwise, animate the icon out first
         if (tv == null) {
             post(launchRunnable);
@@ -599,7 +551,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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index 8409227a..e0298ab 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -28,8 +28,6 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.animation.LinearInterpolator;
-import com.android.systemui.recents.misc.Console;
-import com.android.systemui.recents.Constants;
 
 /**
  * This class facilitates swipe to dismiss. It defines an interface to be implemented by the
@@ -178,11 +176,6 @@
     }
 
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.TouchEvents,
-                    "[SwipeHelper|interceptTouchEvent]",
-                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
-        }
         final int action = ev.getAction();
 
         switch (action) {
@@ -294,12 +287,6 @@
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.TouchEvents,
-                    "[SwipeHelper|touchEvent]",
-                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
-        }
-
         if (!mDragging) {
             if (!onInterceptTouchEvent(ev)) {
                 return mCanCurrViewBeDimissed;
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 adc808a..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;
@@ -167,12 +167,9 @@
         requestSynchronizeStackViewsWithModel(0);
     }
     void requestSynchronizeStackViewsWithModel(int duration) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
-                    "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
-        }
         if (!mStackViewsDirty) {
             invalidate(mStackAlgorithm.mStackRect);
+            mStackViewsDirty = true;
         }
         if (mAwaitingFirstLayout) {
             // Skip the animation if we are awaiting first layout
@@ -180,7 +177,6 @@
         } else {
             mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
         }
-        mStackViewsDirty = true;
     }
 
     /** Returns a mapping of child view to Task. */
@@ -266,11 +262,6 @@
         if (visibleRangeOut != null) {
             visibleRangeOut[0] = frontMostVisibleIndex;
             visibleRangeOut[1] = backMostVisibleIndex;
-            if (Console.Enabled) {
-                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
-                        "[TaskStackView|updateStackTransforms]",
-                        "Back: " + backMostVisibleIndex + " Front: " + frontMostVisibleIndex);
-            }
         }
     }
 
@@ -290,11 +281,6 @@
 
     /** Synchronizes the views with the model */
     void synchronizeStackViewsWithModel() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
-                    "[TaskStackView|synchronizeViewsWithModel]",
-                    "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
-        }
         if (mStackViewsDirty) {
             // Get all the task transforms
             ArrayList<Task> tasks = mStack.getTasks();
@@ -344,11 +330,6 @@
                         mStackViewsAnimationDuration);
             }
 
-            if (Console.Enabled) {
-                Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
-                        "  [TaskStackView|viewChildren]", "" + getChildCount());
-            }
-
             mStackViewsAnimationDuration = 0;
             mStackViewsDirty = false;
         }
@@ -357,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);
@@ -379,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);
@@ -395,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;
@@ -537,11 +532,6 @@
         mMaxScroll = mStackAlgorithm.mMaxScroll;
 
         // Debug logging
-        if (Constants.Log.UI.MeasureAndLayout) {
-            Console.log("  [TaskStack|minScroll] " + mMinScroll);
-            Console.log("  [TaskStack|maxScroll] " + mMaxScroll);
-        }
-
         if (boundScrollToNewMinMax) {
             boundScroll();
         }
@@ -563,9 +553,6 @@
 
     /** Focuses the task at the specified index in the stack */
     void focusTask(int taskIndex, boolean scrollToNewPosition) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex);
-        }
         if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
             mFocusedTaskIndex = taskIndex;
 
@@ -575,9 +562,6 @@
             Runnable postScrollRunnable = null;
             if (tv != null) {
                 tv.setFocusedTask();
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus");
-                }
             } else {
                 postScrollRunnable = new Runnable() {
                     @Override
@@ -586,10 +570,6 @@
                         TaskView tv = getChildViewForTask(t);
                         if (tv != null) {
                             tv.setFocusedTask();
-                            if (Console.Enabled) {
-                                Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]",
-                                        "Requesting focus after scroll animation");
-                            }
                         }
                     }
                 };
@@ -611,11 +591,6 @@
 
     /** Focuses the next task in the stack */
     void focusNextTask(boolean forward) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" +
-                    mFocusedTaskIndex);
-        }
-
         // Find the next index to focus
         int numTasks = mStack.getTaskCount();
         if (mFocusedTaskIndex < 0) {
@@ -630,24 +605,12 @@
 
     /** Enables the hw layers and increments the hw layer requirement ref count */
     void addHwLayersRefCount(String reason) {
-        if (Console.Enabled) {
-            int refCount = mHwLayersTrigger.getCount();
-            Console.log(Constants.Log.UI.HwLayers,
-                    "[TaskStackView|addHwLayersRefCount] refCount: " +
-                            refCount + "->" + (refCount + 1) + " " + reason);
-        }
         mHwLayersTrigger.increment();
     }
 
     /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
         need them anymore. */
     void decHwLayersRefCount(String reason) {
-        if (Console.Enabled) {
-            int refCount = mHwLayersTrigger.getCount();
-            Console.log(Constants.Log.UI.HwLayers,
-                    "[TaskStackView|decHwLayersRefCount] refCount: " +
-                            refCount + "->" + (refCount - 1) + " " + reason);
-        }
         mHwLayersTrigger.decrement();
     }
 
@@ -665,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()) {
@@ -676,10 +638,6 @@
 
     @Override
     public void dispatchDraw(Canvas canvas) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
-                    Console.AnsiPurple);
-        }
         synchronizeStackViewsWithModel();
         clipTaskViews();
         super.dispatchDraw(canvas);
@@ -703,25 +661,12 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]",
-                    "width: " + width + " height: " + height +
-                            " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
-        }
 
         // Compute our stack/task rects
         Rect taskStackBounds = new Rect();
         mConfig.getTaskStackBounds(width, height, taskStackBounds);
         computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
 
-        // Debug logging
-        if (Constants.Log.UI.MeasureAndLayout) {
-            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
-            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
-            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
-            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
-        }
-
         // If this is the first layout, then scroll to the front of the stack and synchronize the
         // stack views immediately
         if (mAwaitingFirstLayout) {
@@ -736,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);
@@ -749,26 +694,13 @@
      */
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]",
-                    "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
-        }
-
-        // Debug logging
-        if (Constants.Log.UI.MeasureAndLayout) {
-            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
-            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
-            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
-            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
-        }
-
         // Layout each of the children
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             TaskView t = (TaskView) getChildAt(i);
             t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
                     mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
-                    mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
+                    mStackAlgorithm.mTaskRect.height() + t.getMaxFooterHeight());
         }
 
         if (mAwaitingFirstLayout) {
@@ -1021,19 +953,12 @@
 
     @Override
     public TaskView createView(Context context) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
-        }
         return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
     }
 
     @Override
     public void prepareViewToEnterPool(TaskView tv) {
         Task task = tv.getTask();
-        if (Console.Enabled) {
-            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
-                    tv.getTask() + " tv: " + tv);
-        }
 
         // Report that this tasks's data is no longer being used
         RecentsTaskLoader.getInstance().unloadTaskData(task);
@@ -1050,11 +975,6 @@
 
     @Override
     public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
-                    "isNewView: " + isNewView);
-        }
-
         // Rebind the task and request that this task's data be filled into the TaskView
         tv.onTaskBound(task);
         RecentsTaskLoader.getInstance().loadTaskData(task);
@@ -1083,10 +1003,6 @@
         }
 
         // Add/attach the view to the hierarchy
-        if (Console.Enabled) {
-            Console.log(Constants.Log.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
-                    "" + insertIndex);
-        }
         if (isNewView) {
             addView(tv, insertIndex);
 
@@ -1112,11 +1028,6 @@
 
     @Override
     public void onTaskViewAppIconClicked(TaskView tv) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
-                    tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
-                    Console.AnsiCyan);
-        }
         if (Constants.DebugFlags.App.EnableTaskFiltering) {
             if (mStack.hasFilteredTasks()) {
                 mStack.unfilterTasks();
@@ -1135,11 +1046,6 @@
 
     @Override
     public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
-                    task + " cb: " + mCb);
-        }
-
         // Cancel any doze triggers
         mUIDozeTrigger.stopDozing();
 
@@ -1163,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/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 15ace13..bd4ea90 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -22,7 +22,6 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewParent;
-import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 
 /* Handles touch events for a TaskStackView. */
@@ -100,12 +99,6 @@
 
     /** Touch preprocessing for handling below */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.TouchEvents,
-                    "[TaskStackViewTouchHandler|interceptTouchEvent]",
-                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
-        }
-
         // Return early if we have no children
         boolean hasChildren = (mSv.getChildCount() > 0);
         if (!hasChildren) {
@@ -186,12 +179,6 @@
 
     /** Handles touch events once we have intercepted them */
     public boolean onTouchEvent(MotionEvent ev) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.TouchEvents,
-                    "[TaskStackViewTouchHandler|touchEvent]",
-                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
-        }
-
         // Short circuit if we have no children
         boolean hasChildren = (mSv.getChildCount() > 0);
         if (!hasChildren) {
@@ -290,16 +277,6 @@
                     int overscrollRange = (int) (Math.min(1f,
                             Math.abs((float) velocity / mMaximumVelocity)) *
                             Constants.Values.TaskStackView.TaskStackOverscrollRange);
-
-                    if (Console.Enabled) {
-                        Console.log(Constants.Log.UI.TouchEvents,
-                                "[TaskStackViewTouchHandler|fling]",
-                                "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
-                                        " maxVelocity: " + mMaximumVelocity +
-                                        " overscrollRange: " + overscrollRange,
-                                Console.AnsiGreen);
-                    }
-
                     // Fling scroll
                     mSv.mScroller.fling(0, mSv.getStackScroll(),
                             0, -velocity,
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 ab14863..199d3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -175,11 +175,6 @@
 
     /** Synchronizes this view's properties with the task's transform */
     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]",
-                    "duration: " + duration, Console.AnsiPurple);
-        }
-
         // Update the bar view
         mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
 
@@ -500,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
@@ -513,7 +508,7 @@
             mThumbnailView.setVisibility(View.VISIBLE);
         }
         mIsStub = isStub;
-    } */
+    }
 
     /**
      * Returns whether this view should be clipped, or any views below should clip against this
@@ -554,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 a3b10f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
+++ /dev/null
@@ -1,272 +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);
-    }
-
-    @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 0f12274..688c0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -18,29 +18,22 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
-import android.widget.ImageButton;
 
-import com.android.systemui.R;
-import com.android.systemui.settings.UserSwitcherHostView;
-import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.tiles.UserDetailView;
 
 /**
  * Container for image of the multi user switcher (tappable).
  */
 public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
 
-    private ViewGroup mOverlayParent;
+    private QSPanel mQsPanel;
 
     public MultiUserSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -52,25 +45,16 @@
         setOnClickListener(this);
     }
 
-    public void setOverlayParent(ViewGroup parent) {
-        mOverlayParent = parent;
+    public void setQsPanel(QSPanel qsPanel) {
+        mQsPanel = qsPanel;
     }
 
     @Override
     public void onClick(View v) {
         final UserManager um = UserManager.get(getContext());
         if (um.isUserSwitcherEnabled()) {
-            final UserSwitcherHostView switcher =
-                    (UserSwitcherHostView) LayoutInflater.from(getContext()).inflate(
-                            R.layout.user_switcher_host, mOverlayParent, false);
-            switcher.setFinishRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    mOverlayParent.removeView(switcher);
-                }
-            });
-            switcher.refreshUsers();
-            mOverlayParent.addView(switcher);
+            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/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 55b3088..fc0f2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -150,7 +150,6 @@
         super.onFinishInflate();
         mHeader = (StatusBarHeaderView) findViewById(R.id.header);
         mHeader.setOnClickListener(this);
-        mHeader.setOverlayParent(this);
         mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
         mQsContainer = findViewById(R.id.quick_settings_container);
         mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
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 505af44..19659ee 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,9 @@
             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);
             }
@@ -2325,6 +2330,9 @@
         if (mCastController != null) {
             mCastController.dump(fd, pw, args);
         }
+        if (mUserSwitcherController != null) {
+            mUserSwitcherController.dump(fd, pw, args);
+        }
     }
 
     private String hunStateToString(Entry entry) {
@@ -2411,11 +2419,9 @@
             String action = intent.getAction();
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
                 int flags = CommandQueue.FLAG_EXCLUDE_NONE;
-                if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
-                    String reason = intent.getStringExtra("reason");
-                    if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
-                        flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
-                    }
+                String reason = intent.getStringExtra("reason");
+                if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
+                    flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
                 }
                 animateCollapsePanels(flags);
             }
@@ -3125,16 +3131,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/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 33d1b15..dc06ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -475,10 +475,6 @@
         });
     }
 
-    public void setOverlayParent(ViewGroup parent) {
-        mMultiUserSwitch.setOverlayParent(parent);
-    }
-
     @Override
     public void onClick(View v) {
         if (v == mSettingsButton) {
@@ -501,6 +497,7 @@
         if (mQSPanel != null) {
             mQSPanel.setCallback(mQsPanelCallback);
         }
+        mMultiUserSwitch.setQsPanel(qsp);
     }
 
     @Override
@@ -589,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/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index ad2cf75..0c5d2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -908,6 +908,9 @@
 
             if (mDialog != null) {
                 mDialog.show();
+                if (mCallback != null) {
+                    mCallback.onVisible(true);
+                }
             }
         }
 
@@ -1160,6 +1163,9 @@
                         mDialog.dismiss();
                         clearRemoteStreamController();
                         mActiveStreamType = -1;
+                        if (mCallback != null) {
+                            mCallback.onVisible(false);
+                        }
                     }
                 }
                 synchronized (sConfirmSafeVolumeLock) {
@@ -1262,5 +1268,6 @@
     public interface Callback {
         void onZenSettings();
         void onInteraction();
+        void onVisible(boolean visible);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index e4f5870..375f94c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -99,6 +99,13 @@
                     kvm.userActivity();
                 }
             }
+
+            @Override
+            public void onVisible(boolean visible) {
+                if (mAudioManager != null && mVolumeController != null) {
+                    mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
+                }
+            }
         });
         mDialogPanel = mPanel;
     }
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 adfa1f2..f431fdb 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -49,6 +49,9 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionLegacyHelper;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -205,6 +208,8 @@
 
     private Drawable mBackgroundDrawable;
 
+    private float mElevation;
+
     private int mFrameResource = 0;
 
     private int mTextColor = 0;
@@ -224,6 +229,7 @@
     private boolean mClosingActionMenu;
 
     private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+    private MediaController mMediaController;
 
     private AudioManager mAudioManager;
     private KeyguardManager mKeyguardManager;
@@ -1688,15 +1694,42 @@
 
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
+            case KeyEvent.KEYCODE_VOLUME_DOWN: {
+                int direction = keyCode == KeyEvent.KEYCODE_VOLUME_UP ? AudioManager.ADJUST_RAISE
+                        : AudioManager.ADJUST_LOWER;
+                // 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);
+                } else {
+                    MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+                            mVolumeControlStreamType, direction, AudioManager.FLAG_SHOW_UI);
+                }
+                return true;
+            }
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                // Similar code is in PhoneFallbackEventHandler in case the window
-                // doesn't have one of these.  In this case, we execute it here and
-                // eat the event instead, because we have mVolumeControlStreamType
-                // and they don't.
                 getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
                 return true;
             }
+            // These are all the recognized media key codes in
+            // KeyEvent.isMediaKey()
+            case KeyEvent.KEYCODE_MEDIA_PLAY:
+            case KeyEvent.KEYCODE_MEDIA_PAUSE:
+            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+            case KeyEvent.KEYCODE_MUTE:
+            case KeyEvent.KEYCODE_HEADSETHOOK:
+            case KeyEvent.KEYCODE_MEDIA_STOP:
+            case KeyEvent.KEYCODE_MEDIA_NEXT:
+            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+            case KeyEvent.KEYCODE_MEDIA_REWIND:
+            case KeyEvent.KEYCODE_MEDIA_RECORD:
+            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+                if (mMediaController != null) {
+                    if (mMediaController.dispatchMediaButtonEvent(event)) {
+                        return true;
+                    }
+                }
+            }
 
             case KeyEvent.KEYCODE_MENU: {
                 onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
@@ -1750,7 +1783,19 @@
 
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
+            case KeyEvent.KEYCODE_VOLUME_DOWN: {
+                // 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
+                            | AudioManager.FLAG_VIBRATE);
+                } else {
+                    MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+                            mVolumeControlStreamType, 0,
+                            AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE);
+                }
+                return true;
+            }
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
                 // Similar code is in PhoneFallbackEventHandler in case the window
                 // doesn't have one of these.  In this case, we execute it here and
@@ -1759,6 +1804,25 @@
                 getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
                 return true;
             }
+            // These are all the recognized media key codes in
+            // KeyEvent.isMediaKey()
+            case KeyEvent.KEYCODE_MEDIA_PLAY:
+            case KeyEvent.KEYCODE_MEDIA_PAUSE:
+            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+            case KeyEvent.KEYCODE_MUTE:
+            case KeyEvent.KEYCODE_HEADSETHOOK:
+            case KeyEvent.KEYCODE_MEDIA_STOP:
+            case KeyEvent.KEYCODE_MEDIA_NEXT:
+            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+            case KeyEvent.KEYCODE_MEDIA_REWIND:
+            case KeyEvent.KEYCODE_MEDIA_RECORD:
+            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+                if (mMediaController != null) {
+                    if (mMediaController.dispatchMediaButtonEvent(event)) {
+                        return true;
+                    }
+                }
+            }
 
             case KeyEvent.KEYCODE_MENU: {
                 onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
@@ -3189,6 +3253,7 @@
                             + Integer.toHexString(mFrameResource));
                 }
             }
+            mElevation = a.getDimension(com.android.internal.R.styleable.Window_windowElevation, 0);
             mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
         }
 
@@ -3278,28 +3343,31 @@
         // Remaining setup -- of background and title -- that only applies
         // to top-level windows.
         if (getContainer() == null) {
-            Drawable drawable = mBackgroundDrawable;
+            final Drawable background;
             if (mBackgroundResource != 0) {
-                drawable = getContext().getDrawable(mBackgroundResource);
+                background = getContext().getDrawable(mBackgroundResource);
+            } else {
+                background = mBackgroundDrawable;
             }
-            mDecor.setWindowBackground(drawable);
-            drawable = null;
+            mDecor.setWindowBackground(background);
+
+            final Drawable frame;
             if (mFrameResource != 0) {
-                drawable = getContext().getDrawable(mFrameResource);
+                frame = getContext().getDrawable(mFrameResource);
+            } else {
+                frame = null;
             }
-            mDecor.setWindowFrame(drawable);
+            mDecor.setWindowFrame(frame);
 
-            // System.out.println("Text=" + Integer.toHexString(mTextColor) +
-            // " Sel=" + Integer.toHexString(mTextSelectedColor) +
-            // " Title=" + Integer.toHexString(mTitleColor));
-
-            if (mTitleColor == 0) {
-                mTitleColor = mTextColor;
-            }
+            mDecor.setElevation(mElevation);
 
             if (mTitle != null) {
                 setTitle(mTitle);
             }
+
+            if (mTitleColor == 0) {
+                mTitleColor = mTextColor;
+            }
             setTitleColor(mTitleColor);
         }
 
@@ -3773,6 +3841,16 @@
         return mVolumeControlStreamType;
     }
 
+    @Override
+    public void setMediaController(MediaController controller) {
+        mMediaController = controller;
+    }
+
+    @Override
+    public MediaController getMediaController() {
+        return mMediaController;
+    }
+
     private boolean isTranslucent() {
         TypedArray a = getWindowStyle();
         return a.getBoolean(a.getResourceId(
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 5bfde4d..c3a9dbe 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3922,6 +3922,11 @@
                                 break;
                         }
 
+                        // Is it a *file* we need to drop?
+                        if (!isRestorableFile(info)) {
+                            okay = false;
+                        }
+
                         // If the policy is satisfied, go ahead and set up to pipe the
                         // data to the agent.
                         if (DEBUG && okay && mAgent != null) {
@@ -4082,9 +4087,9 @@
                             }
                         }
 
-                        // Problems setting up the agent communication, or an already-
-                        // ignored package: skip to the next tar stream entry by
-                        // reading and discarding this file.
+                        // Problems setting up the agent communication, an explicitly
+                        // dropped file, or an already-ignored package: skip to the
+                        // next stream entry by reading and discarding this file.
                         if (!okay) {
                             if (DEBUG) Slog.d(TAG, "[discarding file content]");
                             long bytesToConsume = (info.size + 511) & ~511;
@@ -4691,6 +4696,31 @@
             return info;
         }
 
+        private boolean isRestorableFile(FileMetadata info) {
+            if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
+                if (MORE_DEBUG) {
+                    Slog.i(TAG, "Dropping cache file path " + info.path);
+                }
+                return false;
+            }
+
+            if (FullBackup.ROOT_TREE_TOKEN.equals(info.domain)) {
+                // It's possible this is "no-backup" dir contents in an archive stream
+                // produced on a device running a version of the OS that predates that
+                // API.  Respect the no-backup intention and don't let the data get to
+                // the app.
+                if (info.path.startsWith("no_backup/")) {
+                    if (MORE_DEBUG) {
+                        Slog.i(TAG, "Dropping no_backup file path " + info.path);
+                    }
+                    return false;
+                }
+            }
+
+            // Otherwise we think this file is good to go
+            return true;
+        }
+
         private void HEXLOG(byte[] block) {
             int offset = 0;
             int todo = block.length;
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cdb3835..1316da1 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1189,6 +1189,9 @@
      */
     private boolean mUserIsMonkey;
 
+    /** Flag whether the device has a recents UI */
+    final boolean mHasRecents;
+
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
 
@@ -1808,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;
             }
@@ -2205,6 +2216,9 @@
         mConfigurationSeq = mConfiguration.seq = 1;
         mProcessCpuTracker.init();
 
+        mHasRecents = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_hasRecents);
+
         mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mStackSupervisor = new ActivityStackSupervisor(this);
@@ -2795,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) {
@@ -5649,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);
@@ -10157,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) {
@@ -12430,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();
@@ -15189,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
@@ -15213,7 +15234,6 @@
             return (app.curAdj=app.maxAdj);
         }
 
-        app.keeping = false;
         app.systemNoUi = false;
 
         // Determine the importance of the process, starting with most
@@ -15458,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
@@ -15550,9 +15567,6 @@
                                 if (!client.cached) {
                                     app.cached = false;
                                 }
-                                if (client.keeping) {
-                                    app.keeping = true;
-                                }
                                 adjType = "service";
                             }
                         }
@@ -15664,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;
@@ -15708,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;
                 }
@@ -15791,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
@@ -16015,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,
@@ -16060,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);
@@ -16076,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;
         }
 
@@ -16181,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;
@@ -16257,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,
@@ -16417,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
@@ -16472,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) {
@@ -17116,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,
@@ -17491,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 1f92bf9..c0f4b83 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -825,7 +825,7 @@
         prev.task.touchActiveTime();
         clearLaunchTime(prev);
         final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
-        if (next == null || next.noDisplay || next.task != prev.task) {
+        if (mService.mHasRecents && (next == null || next.noDisplay || next.task != prev.task)) {
             prev.updateThumbnail(screenshotActivities(prev), null);
         }
         stopFullyDrawnTraceIfNeeded();
@@ -1052,6 +1052,14 @@
         next.idle = false;
         next.results = null;
         next.newIntents = null;
+
+        if (next.isHomeActivity() && next.isNotResolverActivity()) {
+            ProcessRecord app = next.task.mActivities.get(0).app;
+            if (app != null && app != mService.mHomeProcess) {
+                mService.mHomeProcess = app;
+            }
+        }
+
         if (next.nowVisible) {
             // We won't get a call to reportActivityVisibleLocked() so dismiss lockscreen now.
             mStackSupervisor.dismissKeyguard();
@@ -1081,6 +1089,7 @@
         if (next == mLastScreenshotActivity) {
             invalidateLastScreenshot();
         }
+        mReturningActivityOptions = null;
     }
 
     private void setVisibile(ActivityRecord r, boolean visible) {
@@ -1134,19 +1143,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 +1183,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 +1219,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 +1649,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 +1843,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 +1855,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 +1873,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 +1899,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 +1954,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 +1979,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 +2015,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;
         }
@@ -2062,6 +2075,8 @@
         final int rootActivityNdx = task.findEffectiveRootIndex();
         for (int i = numActivities - 1; i > rootActivityNdx; --i ) {
             ActivityRecord target = activities.get(i);
+            if (target.frontOfTask)
+                break;
 
             final int flags = target.info.flags;
             final boolean finishOnTaskLaunch =
@@ -2218,6 +2233,8 @@
         // Do not operate on or below the effective root Activity.
         for (int i = numActivities - 1; i > rootActivityNdx; --i) {
             ActivityRecord target = activities.get(i);
+            if (target.frontOfTask)
+                break;
 
             final int flags = target.info.flags;
             boolean finishOnTaskLaunch = (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
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/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 44b0f28eb..f75bdab 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -89,9 +89,15 @@
     // If true, TV going to standby mode puts other devices also to standby.
     private boolean mAutoDeviceOff;
 
+    // If true, TV wakes itself up when receiving <Text/Image View On>.
+    private boolean mAutoWakeup;
+
     HdmiCecLocalDeviceTv(HdmiControlService service) {
         super(service, HdmiCecDeviceInfo.DEVICE_TV);
         mPrevPortId = Constants.INVALID_PORT_ID;
+        mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
+                true);
+        mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
     }
 
     @Override
@@ -121,8 +127,7 @@
     @ServiceThreadOnly
     protected void setPreferredAddress(int addr) {
         assertRunOnServiceThread();
-        SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
-                String.valueOf(addr));
+        SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV, String.valueOf(addr));
     }
 
     private void registerAudioPortUpdateListener() {
@@ -491,7 +496,7 @@
     @ServiceThreadOnly
     protected boolean handleTextViewOn(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (mService.isPowerStandbyOrTransient()) {
+        if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
             mService.wakeUp();
         }
         // TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel
@@ -1115,6 +1120,14 @@
     void setAutoDeviceOff(boolean enabled) {
         assertRunOnServiceThread();
         mAutoDeviceOff = enabled;
+        mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, enabled);
+    }
+
+    @ServiceThreadOnly
+    void setAutoWakeup(boolean enabled) {
+        assertRunOnServiceThread();
+        mAutoWakeup = enabled;
+        mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, enabled);
     }
 
     @Override
@@ -1176,7 +1189,7 @@
         if (!mService.isControlEnabled()) {
             return;
         }
-        if (!initiatedByCec) {
+        if (!initiatedByCec && mAutoDeviceOff) {
             mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
                     mAddress, Constants.ADDR_BROADCAST));
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a9a391b..7db5f0c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -44,7 +44,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings.Global;
-import android.provider.Settings.SettingNotFoundException;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
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/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index 8222155..b3419c1 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -62,6 +62,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
@@ -70,6 +71,7 @@
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
 import android.telephony.gsm.GsmCellLocation;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.NtpTrustedTime;
 
@@ -85,6 +87,8 @@
 import java.util.Map.Entry;
 import java.util.Properties;
 
+import libcore.io.IoUtils;
+
 /**
  * A GPS implementation of LocationProvider used by LocationManager.
  *
@@ -201,7 +205,9 @@
     private static final int AGPS_SETID_TYPE_IMSI = 1;
     private static final int AGPS_SETID_TYPE_MSISDN = 2;
 
-    private static final String PROPERTIES_FILE = "/etc/gps.conf";
+    private static final String PROPERTIES_FILE_PREFIX = "/etc/gps";
+    private static final String PROPERTIES_FILE_SUFFIX = ".conf";
+    private static final String DEFAULT_PROPERTIES_FILE = PROPERTIES_FILE_PREFIX + PROPERTIES_FILE_SUFFIX;
 
     private static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L;
     private static final int GPS_GEOFENCE_AVAILABLE = 1<<1L;
@@ -441,6 +447,44 @@
         return native_is_supported();
     }
 
+    private boolean loadPropertiesFile(String filename) {
+        mProperties = new Properties();
+        try {
+            File file = new File(filename);
+            FileInputStream stream = null;
+            try {
+                stream = new FileInputStream(file);
+                mProperties.load(stream);
+            } finally {
+                IoUtils.closeQuietly(stream);
+            }
+
+            mSuplServerHost = mProperties.getProperty("SUPL_HOST");
+            String portString = mProperties.getProperty("SUPL_PORT");
+            if (mSuplServerHost != null && portString != null) {
+                try {
+                    mSuplServerPort = Integer.parseInt(portString);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
+                }
+            }
+
+            mC2KServerHost = mProperties.getProperty("C2K_HOST");
+            portString = mProperties.getProperty("C2K_PORT");
+            if (mC2KServerHost != null && portString != null) {
+                try {
+                    mC2KServerPort = Integer.parseInt(portString);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "unable to parse C2K_PORT: " + portString);
+                }
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Could not open GPS configuration file " + filename);
+            return false;
+        }
+        return true;
+    }
+
     public GpsLocationProvider(Context context, ILocationManager ilocationManager,
             Looper looper) {
         mContext = context;
@@ -469,34 +513,15 @@
         mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
-        mProperties = new Properties();
-        try {
-            File file = new File(PROPERTIES_FILE);
-            FileInputStream stream = new FileInputStream(file);
-            mProperties.load(stream);
-            stream.close();
+        boolean propertiesLoaded = false;
+        final String gpsHardware = SystemProperties.get("ro.hardware.gps");
+        if (!TextUtils.isEmpty(gpsHardware)) {
+            final String propFilename = PROPERTIES_FILE_PREFIX + "." + gpsHardware + PROPERTIES_FILE_SUFFIX;
+            propertiesLoaded = loadPropertiesFile(propFilename);
+        }
 
-            mSuplServerHost = mProperties.getProperty("SUPL_HOST");
-            String portString = mProperties.getProperty("SUPL_PORT");
-            if (mSuplServerHost != null && portString != null) {
-                try {
-                    mSuplServerPort = Integer.parseInt(portString);
-                } catch (NumberFormatException e) {
-                    Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
-                }
-            }
-
-            mC2KServerHost = mProperties.getProperty("C2K_HOST");
-            portString = mProperties.getProperty("C2K_PORT");
-            if (mC2KServerHost != null && portString != null) {
-                try {
-                    mC2KServerPort = Integer.parseInt(portString);
-                } catch (NumberFormatException e) {
-                    Log.e(TAG, "unable to parse C2K_PORT: " + portString);
-                }
-            }
-        } catch (IOException e) {
-            Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
+        if (!propertiesLoaded) {
+            loadPropertiesFile(DEFAULT_PROPERTIES_FILE);
         }
 
         // construct handler, listen for events
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
deleted file mode 100644
index b31153b..0000000
--- a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.media;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.media.routeprovider.IRouteConnection;
-import android.media.routeprovider.IRouteProvider;
-import android.media.routeprovider.IRouteProviderCallback;
-import android.media.routeprovider.RouteProviderService;
-import android.media.routeprovider.RouteRequest;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Slog;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * System representation and interface to a MediaRouteProvider. This class is
- * not thread safe so all calls should be made on the main thread.
- */
-public class MediaRouteProviderProxy {
-    private static final String TAG = "MRPProxy";
-    private static final boolean DEBUG = true;
-
-    private static final int MAX_RETRIES = 3;
-
-    private final Object mLock = new Object();
-    private final Context mContext;
-    private final String mId;
-    private final ComponentName mComponentName;
-    private final int mUserId;
-    // Interfaces declared in the manifest
-    private final ArrayList<String> mInterfaces = new ArrayList<String>();
-    private final ArrayList<RouteConnectionRecord> mConnections
-            = new ArrayList<RouteConnectionRecord>();
-    // The sessions that have a route from this provider selected
-    private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
-    private final Handler mHandler = new Handler();
-
-    private Intent mBindIntent;
-    private IRouteProvider mBinder;
-    private boolean mRunning;
-    private boolean mPaused;
-    private boolean mInterested;
-    private boolean mBound;
-    private int mRetryCount;
-
-    private RoutesListener mRouteListener;
-
-    public MediaRouteProviderProxy(Context context, String id, ComponentName component, int uid,
-            ArrayList<String> interfaces) {
-        mContext = context;
-        mId = id;
-        mComponentName = component;
-        mUserId = uid;
-        if (interfaces != null) {
-            mInterfaces.addAll(interfaces);
-        }
-        mBindIntent = new Intent(RouteProviderService.SERVICE_INTERFACE);
-        mBindIntent.setComponent(mComponentName);
-    }
-
-    public void destroy() {
-        stop();
-        mSessions.clear();
-        updateBinding();
-    }
-
-    /**
-     * Send any cleanup messages and unbind from the media route provider
-     */
-    public void stop() {
-        if (mRunning) {
-            mRunning = false;
-            mRetryCount = 0;
-            updateBinding();
-        }
-    }
-
-    /**
-     * Bind to the media route provider and perform any setup needed
-     */
-    public void start() {
-        if (!mRunning) {
-            mRunning = true;
-            updateBinding();
-        }
-    }
-
-    /**
-     * Set whether or not this provider is currently interesting to the system.
-     * In the future this may take a list of interfaces instead.
-     *
-     * @param interested True if we want to connect to this provider
-     */
-    public void setInterested(boolean interested) {
-        mInterested = interested;
-        updateBinding();
-    }
-
-    /**
-     * Set a listener to get route updates on.
-     *
-     * @param listener The listener to receive updates on.
-     */
-    public void setRoutesListener(RoutesListener listener) {
-        mRouteListener = listener;
-    }
-
-    /**
-     * Send a request to the Provider to get all the routes that the session can
-     * use.
-     *
-     * @param record The session to get routes for.
-     * @param requestId An id to identify this request.
-     */
-    public void getRoutes(MediaSessionRecord record, final int requestId) {
-        // TODO change routes to have a system global id and maintain a mapping
-        // to the original route
-        if (mBinder == null) {
-            Log.wtf(TAG, "Attempted to call getRoutes without a binder connection");
-            return;
-        }
-        List<RouteRequest> requests = record.getRouteRequests();
-        final String sessionId = record.getSessionInfo().getId();
-        try {
-            mBinder.getAvailableRoutes(requests, new ResultReceiver(mHandler) {
-                @Override
-                protected void onReceiveResult(int resultCode, Bundle resultData) {
-                    if (resultCode != RouteProviderService.RESULT_SUCCESS) {
-                        // ignore failures, just means no routes were generated
-                        return;
-                    }
-                    ArrayList<RouteInfo> routes
-                            = resultData.getParcelableArrayList(RouteProviderService.KEY_ROUTES);
-                    ArrayList<RouteInfo> sysRoutes = new ArrayList<RouteInfo>();
-                    for (int i = 0; i < routes.size(); i++) {
-                        RouteInfo route = routes.get(i);
-                        RouteInfo.Builder bob = new RouteInfo.Builder(route);
-                        bob.setProviderId(mId);
-                        sysRoutes.add(bob.build());
-                    }
-                    if (mRouteListener != null) {
-                        mRouteListener.onRoutesUpdated(sessionId, sysRoutes, requestId);
-                    }
-                }
-            });
-        } catch (RemoteException e) {
-            Log.d(TAG, "Error in getRoutes", e);
-        }
-    }
-
-    /**
-     * Try connecting again if we've been disconnected.
-     */
-    public void rebindIfDisconnected() {
-        if (mBinder == null && shouldBind()) {
-            unbind();
-            bind();
-        }
-    }
-
-    /**
-     * Send a request to connect to a route.
-     *
-     * @param session The session that is trying to connect.
-     * @param route The route it is connecting to.
-     * @param request The request with the connection parameters.
-     * @return true if the request was sent, false otherwise.
-     */
-    public boolean connectToRoute(MediaSessionRecord session, final RouteInfo route,
-            final RouteRequest request) {
-        final String sessionId = session.getSessionInfo().getId();
-        try {
-            mBinder.connect(route, request, new ResultReceiver(mHandler) {
-                @Override
-                protected void onReceiveResult(int resultCode, Bundle resultData) {
-                    if (resultCode != RouteProviderService.RESULT_SUCCESS) {
-                        // TODO handle connection failure
-                        return;
-                    }
-                    IBinder binder = resultData.getBinder(RouteProviderService.KEY_CONNECTION);
-                    IRouteConnection connection = null;
-                    if (binder != null) {
-                        connection = IRouteConnection.Stub.asInterface(binder);
-                    }
-
-                    if (connection != null) {
-                        RouteConnectionRecord record = new RouteConnectionRecord(
-                                connection, mComponentName.getPackageName(), mUserId);
-                        mConnections.add(record);
-                        if (mRouteListener != null) {
-                            mRouteListener.onRouteConnected(sessionId, route, request, record);
-                        }
-                    }
-                }
-            });
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error connecting to route.", e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Check if this is the provider you're looking for.
-     */
-    public boolean hasComponentName(String packageName, String className) {
-        return mComponentName.getPackageName().equals(packageName)
-                && mComponentName.getClassName().equals(className);
-    }
-
-    /**
-     * Get the unique id for this provider.
-     *
-     * @return The provider's id.
-     */
-    public String getId() {
-        return mId;
-    }
-
-    public void addSession(MediaSessionRecord session) {
-        mSessions.add(session);
-    }
-
-    public void removeSession(MediaSessionRecord session) {
-        mSessions.remove(session);
-        updateBinding();
-    }
-
-    public int getSessionCount() {
-        return mSessions.size();
-    }
-
-    public void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + mId + " " + this);
-        String indent = prefix + "  ";
-
-        pw.println(indent + "component=" + mComponentName.toString());
-        pw.println(indent + "user id=" + mUserId);
-        pw.println(indent + "interfaces=" + mInterfaces.toString());
-        pw.println(indent + "connections=" + mConnections.toString());
-        pw.println(indent + "running=" + mRunning);
-        pw.println(indent + "interested=" + mInterested);
-        pw.println(indent + "bound=" + mBound);
-    }
-
-    private void updateBinding() {
-        if (shouldBind()) {
-            bind();
-        } else {
-            unbind();
-        }
-    }
-
-    // We want to bind as long as we're interested in this provider or there are
-    // sessions connected to it.
-    private boolean shouldBind() {
-        return (mRunning && mInterested) || (!mSessions.isEmpty());
-    }
-
-    private void bind() {
-        if (!mBound) {
-            if (DEBUG) {
-                Slog.d(TAG, this + ": Binding");
-            }
-
-            try {
-                mBound = mContext.bindServiceAsUser(mBindIntent, mServiceConn,
-                        Context.BIND_AUTO_CREATE, new UserHandle(mUserId));
-                if (!mBound && DEBUG) {
-                    Slog.d(TAG, this + ": Bind failed");
-                }
-            } catch (SecurityException ex) {
-                if (DEBUG) {
-                    Slog.d(TAG, this + ": Bind failed", ex);
-                }
-            }
-        }
-    }
-
-    private void unbind() {
-        if (mBound) {
-            if (DEBUG) {
-                Slog.d(TAG, this + ": Unbinding");
-            }
-
-            mBound = false;
-            mContext.unbindService(mServiceConn);
-        }
-    }
-
-    private RouteConnectionRecord getConnectionLocked(IBinder binder) {
-        for (int i = mConnections.size() - 1; i >= 0; i--) {
-            RouteConnectionRecord record = mConnections.get(i);
-            if (record.isConnection(binder)) {
-                return record;
-            }
-        }
-        return null;
-    }
-
-    private ServiceConnection mServiceConn = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            mBinder = IRouteProvider.Stub.asInterface(service);
-            if (DEBUG) {
-                Slog.d(TAG, "Connected to route provider");
-            }
-            try {
-                mBinder.registerCallback(mCbStub);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Error registering callback on route provider. Retry count: "
-                        + mRetryCount, e);
-                if (mRetryCount < MAX_RETRIES) {
-                    mRetryCount++;
-                    rebindIfDisconnected();
-                }
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            mBinder = null;
-            if (DEBUG) {
-                Slog.d(TAG, "Disconnected from route provider");
-            }
-        }
-
-    };
-
-    private IRouteProviderCallback.Stub mCbStub = new IRouteProviderCallback.Stub() {
-        @Override
-        public void onConnectionStateChanged(IRouteConnection connection, int state)
-                throws RemoteException {
-            // TODO
-        }
-
-        @Override
-        public void onRouteEvent(RouteEvent event) throws RemoteException {
-            synchronized (mLock) {
-                RouteConnectionRecord record = getConnectionLocked(event.getConnection());
-                Log.d(TAG, "Received route event for record " + record);
-                if (record != null) {
-                    record.sendEvent(event);
-                }
-            }
-        }
-
-        @Override
-        public void onConnectionTerminated(IRouteConnection connection) throws RemoteException {
-            synchronized (mLock) {
-                RouteConnectionRecord record = getConnectionLocked(connection.asBinder());
-                if (record != null) {
-                    record.disconnect();
-                    mConnections.remove(record);
-                }
-            }
-        }
-
-        @Override
-        public void onRoutesChanged() throws RemoteException {
-            // TODO
-        }
-    };
-
-    /**
-     * Listener for receiving responses to route requests on the provider.
-     */
-    public interface RoutesListener {
-        /**
-         * Called when routes have been returned from a request to getRoutes.
-         *
-         * @param record The session that the routes were requested for.
-         * @param routes The matching routes returned by the provider.
-         * @param reqId The request id this is responding to.
-         */
-        public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
-                int reqId);
-
-        /**
-         * Called when a route has successfully connected.
-         *
-         * @param session The session that was connected.
-         * @param route The route it connected to.
-         * @param options The options that were used for the connection.
-         * @param connection The connection instance that was created.
-         */
-        public void onRouteConnected(String sessionId, RouteInfo route,
-                RouteRequest options, RouteConnectionRecord connection);
-    }
-}
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java b/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
deleted file mode 100644
index 734eab9..0000000
--- a/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.media;
-
-import android.Manifest;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.media.routeprovider.RouteProviderService;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.UUID;
-
-/**
- * Watches for media route provider services to be installed. Adds a provider to
- * the media session service for each registered service. For now just run all
- * providers. In the future define a policy for when to run providers.
- */
-public class MediaRouteProviderWatcher {
-    private static final String TAG = "MRPWatcher";
-    private static final boolean DEBUG = true; // Log.isLoggable(TAG,
-                                               // Log.DEBUG);
-
-    private final Context mContext;
-    private final Callback mCallback;
-    private final Handler mHandler;
-    private final int mUserId;
-    private final PackageManager mPackageManager;
-
-    private final ArrayList<MediaRouteProviderProxy> mProviders =
-            new ArrayList<MediaRouteProviderProxy>();
-    private boolean mRunning;
-
-    public MediaRouteProviderWatcher(Context context, Callback callback, Handler handler,
-            int userId) {
-        mContext = context;
-        mCallback = callback;
-        mHandler = handler;
-        mUserId = userId;
-        mPackageManager = context.getPackageManager();
-    }
-
-    public void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + " mUserId=" + mUserId);
-        pw.println(prefix + " mRunning=" + mRunning);
-        pw.println(prefix + " mProviders.size()=" + mProviders.size());
-    }
-
-    public void start() {
-        if (!mRunning) {
-            mRunning = true;
-
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
-            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
-            filter.addDataScheme("package");
-            mContext.registerReceiverAsUser(mScanPackagesReceiver,
-                    new UserHandle(mUserId), filter, null, mHandler);
-
-            // Scan packages.
-            // Also has the side-effect of restarting providers if needed.
-            mHandler.post(mScanPackagesRunnable);
-        }
-    }
-
-    // Stop discovering providers and routes. Providers that still have an
-    // active session connected to them will not unbind.
-    public void stop() {
-        if (mRunning) {
-            mRunning = false;
-
-            mContext.unregisterReceiver(mScanPackagesReceiver);
-            mHandler.removeCallbacks(mScanPackagesRunnable);
-
-            // Stop all inactive providers.
-            for (int i = mProviders.size() - 1; i >= 0; i--) {
-                mProviders.get(i).stop();
-            }
-        }
-    }
-
-    // Clean up the providers forcibly unbinding if necessary
-    public void destroy() {
-        for (int i = mProviders.size() - 1; i >= 0; i--) {
-            mProviders.get(i).destroy();
-            mProviders.remove(i);
-        }
-    }
-
-    public ArrayList<MediaRouteProviderProxy> getProviders() {
-        return mProviders;
-    }
-
-    public MediaRouteProviderProxy getProvider(String id) {
-        int providerIndex = findProvider(id);
-        if (providerIndex != -1) {
-            return mProviders.get(providerIndex);
-        }
-        return null;
-    }
-
-    private void scanPackages() {
-        if (!mRunning) {
-            return;
-        }
-
-        // Add providers for all new services.
-        // Reorder the list so that providers left at the end will be the ones
-        // to remove.
-        int targetIndex = 0;
-        Intent intent = new Intent(RouteProviderService.SERVICE_INTERFACE);
-        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
-                intent, 0, mUserId)) {
-            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-            if (DEBUG) {
-                Slog.d(TAG, "Checking service " + (serviceInfo == null ? null : serviceInfo.name));
-            }
-            if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
-                int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
-                if (sourceIndex < 0) {
-                    // TODO get declared interfaces from manifest
-                    if (DEBUG) {
-                        Slog.d(TAG, "Creating new provider proxy for service");
-                    }
-                    MediaRouteProviderProxy provider =
-                            new MediaRouteProviderProxy(mContext, UUID.randomUUID().toString(),
-                                    new ComponentName(serviceInfo.packageName, serviceInfo.name),
-                                    mUserId, null);
-                    provider.start();
-                    mProviders.add(targetIndex++, provider);
-                    mCallback.addProvider(provider);
-                } else if (sourceIndex >= targetIndex) {
-                    MediaRouteProviderProxy provider = mProviders.get(sourceIndex);
-                    provider.start(); // restart the provider if needed
-                    provider.rebindIfDisconnected();
-                    Collections.swap(mProviders, sourceIndex, targetIndex++);
-                }
-            }
-        }
-
-        // Remove providers for missing services.
-        if (targetIndex < mProviders.size()) {
-            for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
-                MediaRouteProviderProxy provider = mProviders.get(i);
-                mCallback.removeProvider(provider);
-                mProviders.remove(provider);
-                provider.stop();
-            }
-        }
-    }
-
-    private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
-        if (serviceInfo.permission == null || !serviceInfo.permission.equals(
-                Manifest.permission.BIND_ROUTE_PROVIDER)) {
-            // If the service does not require this permission then any app
-            // could potentially bind to it and mess with their routes. So we
-            // only want to trust providers that require the
-            // correct permissions.
-            Slog.w(TAG, "Ignoring route provider service because it did not "
-                    + "require the BIND_ROUTE_PROVIDER permission in its manifest: "
-                    + serviceInfo.packageName + "/" + serviceInfo.name);
-            return false;
-        }
-        // Looks good.
-        return true;
-    }
-
-    private int findProvider(String id) {
-        int count = mProviders.size();
-        for (int i = 0; i < count; i++) {
-            MediaRouteProviderProxy provider = mProviders.get(i);
-            if (TextUtils.equals(id, provider.getId())) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private int findProvider(String packageName, String className) {
-        int count = mProviders.size();
-        for (int i = 0; i < count; i++) {
-            MediaRouteProviderProxy provider = mProviders.get(i);
-            if (provider.hasComponentName(packageName, className)) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
-            @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) {
-                Slog.d(TAG, "Received package manager broadcast: " + intent);
-            }
-            scanPackages();
-        }
-    };
-
-    private final Runnable mScanPackagesRunnable = new Runnable() {
-            @Override
-        public void run() {
-            scanPackages();
-        }
-    };
-
-    public interface Callback {
-        void addProvider(MediaRouteProviderProxy provider);
-
-        void removeProvider(MediaRouteProviderProxy provider);
-    }
-}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0fbcd7e..01a21f4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -19,19 +19,16 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.media.routeprovider.RouteRequest;
+import android.media.routing.IMediaRouter;
+import android.media.routing.IMediaRouterDelegate;
+import android.media.routing.IMediaRouterStateCallback;
 import android.media.session.ISessionController;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.MediaController;
-import android.media.session.RouteCommand;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.media.session.RouteEvent;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionInfo;
-import android.media.session.RouteInterface;
 import android.media.session.PlaybackState;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.AudioManager;
@@ -94,14 +91,9 @@
     private final Object mLock = new Object();
     private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
             new ArrayList<ISessionControllerCallback>();
-    private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
 
-    private RouteInfo mRoute;
-    private RouteOptions mRequest;
-    private RouteConnectionRecord mConnection;
-    // TODO define a RouteState class with relevant info
-    private int mRouteState;
     private long mFlags;
+    private IMediaRouter mMediaRouter;
     private ComponentName mMediaButtonReceiver;
 
     // TransportPerformer fields
@@ -160,24 +152,6 @@
     }
 
     /**
-     * Get the set of route requests this session is interested in.
-     *
-     * @return The list of RouteRequests
-     */
-    public List<RouteRequest> getRouteRequests() {
-        return mRequests;
-    }
-
-    /**
-     * Get the route this session is currently on.
-     *
-     * @return The route the session is on.
-     */
-    public RouteInfo getRoute() {
-        return mRoute;
-    }
-
-    /**
      * Get the info for this session.
      *
      * @return Info that identifies this session.
@@ -229,41 +203,6 @@
     }
 
     /**
-     * Set the selected route. This does not connect to the route, just notifies
-     * the app that a new route has been selected.
-     *
-     * @param route The route that was selected.
-     */
-    public void selectRoute(RouteInfo route) {
-        synchronized (mLock) {
-            if (route != mRoute) {
-                disconnect(MediaSession.DISCONNECT_REASON_ROUTE_CHANGED);
-            }
-            mRoute = route;
-        }
-        mSessionCb.sendRouteChange(route);
-    }
-
-    /**
-     * Update the state of the route this session is using and notify the
-     * session.
-     *
-     * @param state The new state of the route.
-     */
-    public void setRouteState(int state) {
-        mSessionCb.sendRouteStateChange(state);
-    }
-
-    /**
-     * Send an event to this session from the route it is using.
-     *
-     * @param event The event to send.
-     */
-    public void sendRouteEvent(RouteEvent event) {
-        mSessionCb.sendRouteEvent(event);
-    }
-
-    /**
      * Send a volume adjustment to the session owner.
      *
      * @param delta The amount to adjust the volume by.
@@ -338,40 +277,6 @@
     }
 
     /**
-     * Set the connection to use for the selected route and notify the app it is
-     * now connected.
-     *
-     * @param route The route the connection is to.
-     * @param request The request that was used to connect.
-     * @param connection The connection to the route.
-     * @return True if this connection is still valid, false if it is stale.
-     */
-    public boolean setRouteConnected(RouteInfo route, RouteOptions request,
-            RouteConnectionRecord connection) {
-        synchronized (mLock) {
-            if (mDestroyed) {
-                Log.i(TAG, "setRouteConnected: session has been destroyed");
-                connection.disconnect();
-                return false;
-            }
-            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
-                Log.w(TAG, "setRouteConnected: connected route is stale");
-                connection.disconnect();
-                return false;
-            }
-            if (request != mRequest) {
-                Log.w(TAG, "setRouteConnected: connection request is stale");
-                connection.disconnect();
-                return false;
-            }
-            mConnection = connection;
-            mConnection.setListener(mConnectionListener);
-            mSessionCb.sendRouteConnected();
-        }
-        return true;
-    }
-
-    /**
      * Check if this session has been set to active by the app.
      *
      * @return True if the session is active, false otherwise.
@@ -460,30 +365,6 @@
         return mOptimisticVolume;
     }
 
-    /**
-     * @return True if this session is currently connected to a route.
-     */
-    public boolean isConnected() {
-        return mConnection != null;
-    }
-
-    public void disconnect(int reason) {
-        synchronized (mLock) {
-            if (!mDestroyed) {
-                disconnectLocked(reason);
-            }
-        }
-    }
-
-    private void disconnectLocked(int reason) {
-        if (mConnection != null) {
-            mConnection.setListener(null);
-            mConnection.disconnect();
-            mConnection = null;
-            pushDisconnected(reason);
-        }
-    }
-
     public boolean isTransportControlEnabled() {
         return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
     }
@@ -502,11 +383,6 @@
             if (mDestroyed) {
                 return;
             }
-            if (isConnected()) {
-                disconnectLocked(MediaSession.DISCONNECT_REASON_SESSION_DESTROYED);
-            }
-            mRoute = null;
-            mRequest = null;
             mDestroyed = true;
         }
     }
@@ -532,15 +408,6 @@
         pw.println(indent + "controllers: " + mControllerCallbacks.size());
         pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
         pw.println(indent + "metadata:" + getShortMetadataString());
-        pw.println(indent + "route requests {");
-        int size = mRequests.size();
-        for (int i = 0; i < size; i++) {
-            pw.println(indent + "  " + mRequests.get(i).toString());
-        }
-        pw.println(indent + "}");
-        pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString()));
-        pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString()));
-        pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString()));
     }
 
     private String getShortMetadataString() {
@@ -550,12 +417,6 @@
         return "size=" + fields + ", title=" + title;
     }
 
-    private void pushDisconnected(int reason) {
-        synchronized (mLock) {
-            mSessionCb.sendRouteDisconnected(reason);
-        }
-    }
-
     private void pushPlaybackStateUpdate() {
         synchronized (mLock) {
             if (mDestroyed) {
@@ -613,25 +474,6 @@
         }
     }
 
-    private void pushRouteUpdate() {
-        synchronized (mLock) {
-            if (mDestroyed) {
-                return;
-            }
-            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
-                ISessionControllerCallback cb = mControllerCallbacks.get(i);
-                try {
-                    cb.onRouteChanged(mRoute);
-                } catch (DeadObjectException e) {
-                    Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
-                    mControllerCallbacks.remove(i);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "unexpected exception in pushRouteUpdate.", e);
-                }
-            }
-        }
-    }
-
     private void pushEvent(String event, Bundle data) {
         synchronized (mLock) {
             if (mDestroyed) {
@@ -651,25 +493,6 @@
         }
     }
 
-    private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
-        synchronized (mLock) {
-            if (mDestroyed) {
-                return;
-            }
-            if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) {
-                if (cb != null) {
-                    cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null);
-                    return;
-                }
-            }
-            if (mConnection != null) {
-                mConnection.sendCommand(command, cb);
-            } else if (cb != null) {
-                cb.send(RouteInterface.RESULT_NOT_CONNECTED, null);
-            }
-        }
-    }
-
     private PlaybackState getStateWithUpdatedPosition() {
         PlaybackState state = mPlaybackState;
         long duration = -1;
@@ -682,16 +505,19 @@
                     || state.getState() == PlaybackState.STATE_FAST_FORWARDING
                     || state.getState() == PlaybackState.STATE_REWINDING) {
                 long updateTime = state.getLastPositionUpdateTime();
+                long currentTime = SystemClock.elapsedRealtime();
                 if (updateTime > 0) {
-                    long position = (long) (state.getPlaybackRate()
-                            * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition();
+                    long position = (long) (state.getPlaybackSpeed()
+                            * (currentTime - updateTime)) + state.getPosition();
                     if (duration >= 0 && position > duration) {
                         position = duration;
                     } else if (position < 0) {
                         position = 0;
                     }
-                    result = new PlaybackState(state);
-                    result.setState(state.getState(), position, state.getPlaybackRate());
+                    PlaybackState.Builder builder = new PlaybackState.Builder(state);
+                    builder.setState(state.getState(), position, state.getPlaybackSpeed(),
+                            currentTime);
+                    result = builder.build();
                 }
             }
         }
@@ -708,21 +534,6 @@
         return -1;
     }
 
-    private final RouteConnectionRecord.Listener mConnectionListener
-            = new RouteConnectionRecord.Listener() {
-        @Override
-        public void onEvent(RouteEvent event) {
-            RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
-                    event.getEvent(), event.getExtras());
-            mSessionCb.sendRouteEvent(eventForSession);
-        }
-
-        @Override
-        public void disconnect() {
-            MediaSessionRecord.this.disconnect(MediaSession.DISCONNECT_REASON_PROVIDER_DISCONNECTED);
-        }
-    };
-
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
         @Override
         public void run() {
@@ -769,6 +580,12 @@
         }
 
         @Override
+        public void setMediaRouter(IMediaRouter router) {
+            mMediaRouter = router;
+            mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
+        }
+
+        @Override
         public void setMediaButtonReceiver(ComponentName mbr) {
             mMediaButtonReceiver = mbr;
         }
@@ -797,46 +614,6 @@
         }
 
         @Override
-        public void sendRouteCommand(RouteCommand command, ResultReceiver cb) {
-            mHandler.post(MessageHandler.MSG_SEND_COMMAND,
-                    new Pair<RouteCommand, ResultReceiver>(command, cb));
-        }
-
-        @Override
-        public boolean setRoute(RouteInfo route) throws RemoteException {
-            // TODO decide if allowed to set route and if the route exists
-            return false;
-        }
-
-        @Override
-        public void connectToRoute(RouteInfo route, RouteOptions request)
-                throws RemoteException {
-            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
-                throw new RemoteException("RouteInfo does not match current route");
-            }
-            mService.connectToRoute(MediaSessionRecord.this, route, request);
-            mRequest = request;
-        }
-
-        @Override
-        public void disconnectFromRoute(RouteInfo route) {
-            if (route != null && mRoute != null
-                    && TextUtils.equals(route.getId(), mRoute.getId())) {
-                disconnect(MediaSession.DISCONNECT_REASON_SESSION_DISCONNECTED);
-            }
-        }
-
-        @Override
-        public void setRouteOptions(List<RouteOptions> options) throws RemoteException {
-            mRequests.clear();
-            for (int i = options.size() - 1; i >= 0; i--) {
-                RouteRequest request = new RouteRequest(mSessionInfo, options.get(i),
-                        false);
-                mRequests.add(request);
-            }
-        }
-
-        @Override
         public void setCurrentVolume(int volume) {
             mCurrentVolume = volume;
             mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
@@ -903,46 +680,6 @@
             }
         }
 
-        public void sendRouteChange(RouteInfo route) {
-            try {
-                mCb.onRequestRouteChange(route);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in sendRouteChange.", e);
-            }
-        }
-
-        public void sendRouteStateChange(int state) {
-            try {
-                mCb.onRouteStateChange(state);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
-            }
-        }
-
-        public void sendRouteEvent(RouteEvent event) {
-            try {
-                mCb.onRouteEvent(event);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
-            }
-        }
-
-        public void sendRouteConnected() {
-            try {
-                mCb.onRouteConnected(mRoute, mRequest);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
-            }
-        }
-
-        public void sendRouteDisconnected(int reason) {
-            try {
-                mCb.onRouteDisconnected(mRoute, reason);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in sendRouteDisconnected");
-            }
-        }
-
         public void play() {
             try {
                 mCb.onPlay();
@@ -1187,20 +924,19 @@
         }
 
         @Override
-        public void showRoutePicker() {
-            mService.showRoutePickerForSession(MediaSessionRecord.this);
+        public IMediaRouterDelegate createMediaRouterDelegate(
+                IMediaRouterStateCallback callback) {
+            // todo
+            return null;
         }
     }
 
     private class MessageHandler extends Handler {
         private static final int MSG_UPDATE_METADATA = 1;
         private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
-        private static final int MSG_UPDATE_ROUTE = 3;
-        private static final int MSG_SEND_EVENT = 4;
-        private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
-        private static final int MSG_SEND_COMMAND = 6;
-        private static final int MSG_UPDATE_SESSION_STATE = 7;
-        private static final int MSG_UPDATE_VOLUME = 8;
+        private static final int MSG_SEND_EVENT = 3;
+        private static final int MSG_UPDATE_SESSION_STATE = 4;
+        private static final int MSG_UPDATE_VOLUME = 5;
 
         public MessageHandler(Looper looper) {
             super(looper);
@@ -1214,17 +950,9 @@
                 case MSG_UPDATE_PLAYBACK_STATE:
                     pushPlaybackStateUpdate();
                     break;
-                case MSG_UPDATE_ROUTE:
-                    pushRouteUpdate();
-                    break;
                 case MSG_SEND_EVENT:
                     pushEvent((String) msg.obj, msg.getData());
                     break;
-                case MSG_SEND_COMMAND:
-                    Pair<RouteCommand, ResultReceiver> cmd =
-                            (Pair<RouteCommand, ResultReceiver>) msg.obj;
-                    pushRouteCommand(cmd.first, cmd.second);
-                    break;
                 case MSG_UPDATE_SESSION_STATE:
                     // TODO add session state
                     break;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 310f3e9..4c475d9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -30,14 +30,11 @@
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
-import android.media.routeprovider.RouteRequest;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -74,15 +71,12 @@
     private static final int WAKELOCK_TIMEOUT = 5000;
 
     private final SessionManagerImpl mSessionManagerImpl;
-    // private final MediaRouteProviderWatcher mRouteProviderWatcher;
     private final MediaSessionStack mPriorityStack;
 
     private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
     private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
     private final ArrayList<SessionsListenerRecord> mSessionsListeners
             = new ArrayList<SessionsListenerRecord>();
-    // private final ArrayList<MediaRouteProviderProxy> mProviders
-    // = new ArrayList<MediaRouteProviderProxy>();
     private final Object mLock = new Object();
     private final MessageHandler mHandler = new MessageHandler();
     private final PowerManager.WakeLock mMediaEventWakeLock;
@@ -94,10 +88,6 @@
     private MediaSessionRecord mPrioritySession;
     private int mCurrentUserId = -1;
 
-    // Used to keep track of the current request to show routes for a specific
-    // session so we drop late callbacks properly.
-    private int mShowRoutesRequestId = 0;
-
     // Used to notify system UI when remote volume was changed. TODO find a
     // better way to handle this.
     private IRemoteVolumeController mRvc;
@@ -126,69 +116,6 @@
         return IAudioService.Stub.asInterface(b);
     }
 
-    /**
-     * Should trigger showing the Media route picker dialog. Right now it just
-     * kicks off a query to all the providers to get routes.
-     *
-     * @param record The session to show the picker for.
-     */
-    public void showRoutePickerForSession(MediaSessionRecord record) {
-        // TODO for now just toggle the route to test (we will only have one
-        // match for now)
-        synchronized (mLock) {
-            if (!mAllSessions.contains(record)) {
-                Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
-                return;
-            }
-            RouteInfo current = record.getRoute();
-            UserRecord user = mUserRecords.get(record.getUserId());
-            if (current != null) {
-                // For now send null to mean the local route
-                MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
-                if (proxy != null) {
-                    proxy.removeSession(record);
-                }
-                record.selectRoute(null);
-                return;
-            }
-            ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
-            mShowRoutesRequestId++;
-            for (int i = providers.size() - 1; i >= 0; i--) {
-                MediaRouteProviderProxy provider = providers.get(i);
-                provider.getRoutes(record, mShowRoutesRequestId);
-            }
-        }
-    }
-
-    /**
-     * Connect a session to the given route.
-     *
-     * @param session The session to connect.
-     * @param route The route to connect to.
-     * @param options The options to use for the connection.
-     */
-    public void connectToRoute(MediaSessionRecord session, RouteInfo route,
-            RouteOptions options) {
-        synchronized (mLock) {
-            if (!mAllSessions.contains(session)) {
-                Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
-                return;
-            }
-            UserRecord user = mUserRecords.get(session.getUserId());
-            if (user == null) {
-                Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
-                return;
-            }
-            MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
-            if (proxy == null) {
-                Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
-                return;
-            }
-            RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
-            proxy.connectToRoute(session, route, request);
-        }
-    }
-
     public void updateSession(MediaSessionRecord record) {
         synchronized (mLock) {
             if (!mAllSessions.contains(record)) {
@@ -552,110 +479,49 @@
         }
     }
 
-    private MediaRouteProviderProxy.RoutesListener mRoutesCallback
-            = new MediaRouteProviderProxy.RoutesListener() {
-        @Override
-        public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
-                int reqId) {
-            // TODO for now select the first route to test, eventually add the
-            // new routes to the dialog if it is still open
-            synchronized (mLock) {
-                int index = findIndexOfSessionForIdLocked(sessionId);
-                if (index != -1 && routes != null && routes.size() > 0) {
-                    MediaSessionRecord record = mAllSessions.get(index);
-                    RouteInfo route = routes.get(0);
-                    record.selectRoute(route);
-                    UserRecord user = mUserRecords.get(record.getUserId());
-                    MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
-                    provider.addSession(record);
-                }
-            }
-        }
-
-        @Override
-        public void onRouteConnected(String sessionId, RouteInfo route,
-                RouteRequest options, RouteConnectionRecord connection) {
-            synchronized (mLock) {
-                int index = findIndexOfSessionForIdLocked(sessionId);
-                if (index != -1) {
-                    MediaSessionRecord session = mAllSessions.get(index);
-                    session.setRouteConnected(route, options.getConnectionOptions(), connection);
-                }
-            }
-        }
-    };
-
     /**
      * Information about a particular user. The contents of this object is
      * guarded by mLock.
      */
     final class UserRecord {
         private final int mUserId;
-        private final MediaRouteProviderWatcher mRouteProviderWatcher;
-        private final ArrayList<MediaRouteProviderProxy> mProviders
-                = new ArrayList<MediaRouteProviderProxy>();
         private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
 
         public UserRecord(Context context, int userId) {
             mUserId = userId;
-            mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
-                    mProviderWatcherCallback, mHandler, userId);
         }
 
         public void startLocked() {
-            mRouteProviderWatcher.start();
+            // nothing for now
         }
 
         public void stopLocked() {
-            mRouteProviderWatcher.stop();
-            updateInterestLocked();
+            // nothing for now
         }
 
         public void destroyLocked() {
             for (int i = mSessions.size() - 1; i >= 0; i--) {
                 MediaSessionRecord session = mSessions.get(i);
                 MediaSessionService.this.destroySessionLocked(session);
-                if (session.isConnected()) {
-                    session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
-                }
             }
         }
 
-        public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
-            return mProviders;
-        }
-
         public ArrayList<MediaSessionRecord> getSessionsLocked() {
             return mSessions;
         }
 
         public void addSessionLocked(MediaSessionRecord session) {
             mSessions.add(session);
-            updateInterestLocked();
         }
 
         public void removeSessionLocked(MediaSessionRecord session) {
             mSessions.remove(session);
-            RouteInfo route = session.getRoute();
-            if (route != null) {
-                MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
-                if (provider != null) {
-                    provider.removeSession(session);
-                }
-            }
-            updateInterestLocked();
         }
 
         public void dumpLocked(PrintWriter pw, String prefix) {
             pw.println(prefix + "Record for user " + mUserId);
             String indent = prefix + "  ";
-            int size = mProviders.size();
-            pw.println(indent + size + " Providers:");
-            for (int i = 0; i < size; i++) {
-                mProviders.get(i).dump(pw, indent);
-            }
-            pw.println();
-            size = mSessions.size();
+            int size = mSessions.size();
             pw.println(indent + size + " Sessions:");
             for (int i = 0; i < size; i++) {
                 // Just print the session info, the full session dump will
@@ -663,48 +529,6 @@
                 pw.println(indent + mSessions.get(i).getSessionInfo());
             }
         }
-
-        public void updateInterestLocked() {
-            // TODO go through the sessions and build up the set of interfaces
-            // we're interested in. Update the provider watcher.
-            // For now, just express interest in all providers for the current
-            // user
-            boolean interested = mUserId == mCurrentUserId;
-            for (int i = mProviders.size() - 1; i >= 0; i--) {
-                mProviders.get(i).setInterested(interested);
-            }
-        }
-
-        private MediaRouteProviderProxy getProviderLocked(String providerId) {
-            for (int i = mProviders.size() - 1; i >= 0; i--) {
-                MediaRouteProviderProxy provider = mProviders.get(i);
-                if (TextUtils.equals(providerId, provider.getId())) {
-                    return provider;
-                }
-            }
-            return null;
-        }
-
-        private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
-                = new MediaRouteProviderWatcher.Callback() {
-            @Override
-            public void removeProvider(MediaRouteProviderProxy provider) {
-                synchronized (mLock) {
-                    mProviders.remove(provider);
-                    provider.setRoutesListener(null);
-                    provider.setInterested(false);
-                }
-            }
-
-            @Override
-            public void addProvider(MediaRouteProviderProxy provider) {
-                synchronized (mLock) {
-                    mProviders.add(provider);
-                    provider.setRoutesListener(mRoutesCallback);
-                    provider.setInterested(true);
-                }
-            }
-        };
     }
 
     final class SessionsListenerRecord implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index e26a2eb..3d1ecb3 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -25,7 +25,7 @@
 
 /**
  * Keeps track of media sessions and their priority for notifications, media
- * button routing, etc.
+ * button dispatch, etc.
  */
 public class MediaSessionStack {
     /**
@@ -277,8 +277,8 @@
                 lastActiveIndex++;
                 lastPublishedIndex++;
             } else if (session.isPlaybackActive(true)) {
-                // TODO replace getRoute() == null with real local route check
-                if(session.getRoute() == null) {
+                // TODO this with real local route check
+                if (true) {
                     // Active local sessions get top priority
                     result.add(lastLocalIndex, session);
                     lastLocalIndex++;
diff --git a/services/core/java/com/android/server/media/RouteConnectionRecord.java b/services/core/java/com/android/server/media/RouteConnectionRecord.java
deleted file mode 100644
index 90ddf29..0000000
--- a/services/core/java/com/android/server/media/RouteConnectionRecord.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.media;
-
-import android.media.routeprovider.IRouteConnection;
-import android.media.session.RouteCommand;
-import android.media.session.RouteEvent;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-/**
- * A connection between a Session and a Route.
- */
-public class RouteConnectionRecord {
-    private static final String TAG = "RouteConnRecord";
-    private final IRouteConnection mBinder;
-    private final String mPackageName;
-    private final int mUid;
-    private Listener mListener;
-
-    public RouteConnectionRecord(IRouteConnection binder, String packageName, int uid) {
-        mBinder = binder;
-        mPackageName = packageName;
-        mUid = uid;
-    }
-
-    /**
-     * Add a listener to get route events on.
-     *
-     * @param listener The listener to get events on.
-     */
-    public void setListener(Listener listener) {
-        mListener = listener;
-    }
-
-    /**
-     * Check if this connection matches the token given.
-     *
-     * @param binder The token to check
-     * @return True if this is the connection you're looking for, false
-     *         otherwise.
-     */
-    public boolean isConnection(IBinder binder) {
-        return binder != null && binder.equals(mBinder.asBinder());
-    }
-
-    /**
-     * Send an event from this connection.
-     *
-     * @param event The event to send.
-     */
-    public void sendEvent(RouteEvent event) {
-        if (mListener != null) {
-            mListener.onEvent(event);
-        }
-    }
-
-    /**
-     * Send a command to this connection.
-     *
-     * @param command The command to send.
-     * @param cb The receiver to get a result on.
-     */
-    public void sendCommand(RouteCommand command, ResultReceiver cb) {
-        try {
-            mBinder.onCommand(command, cb);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error in sendCommand", e);
-        }
-    }
-
-    /**
-     * Tell the session that the provider has disconnected it.
-     */
-    public void disconnect() {
-        if (mListener != null) {
-            mListener.disconnect();
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "RouteConnection { binder=" + mBinder.toString() + ", package=" + mPackageName
-                + ", uid=" + mUid + "}";
-    }
-
-    /**
-     * Listener to receive updates from the provider for this connection.
-     */
-    public static interface Listener {
-        /**
-         * Called when an event is sent on this connection.
-         *
-         * @param event The event that was sent.
-         */
-        public void onEvent(RouteEvent event);
-
-        /**
-         * Called when the provider has disconnected the route.
-         */
-        public void disconnect();
-    }
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 08c8271..f4248daf 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/telecomm/java/android/telecomm/CallServiceDescriptor.aidl b/services/core/java/com/android/server/notification/RankingConfig.java
similarity index 67%
copy from telecomm/java/android/telecomm/CallServiceDescriptor.aidl
copy to services/core/java/com/android/server/notification/RankingConfig.java
index f517c73..7d0bd59 100644
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -1,5 +1,5 @@
-/*
- * Copyright 2014, The Android Open Source Project
+/**
+ * Copyright (c) 2014, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.server.notification;
 
-package android.telecomm;
+public interface RankingConfig {
+    int getPackagePriority(String packageName, int uid);
 
-parcelable CallServiceDescriptor;
+    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/KeySetHandle.java b/services/core/java/com/android/server/pm/KeySetHandle.java
new file mode 100644
index 0000000..640feb3
--- /dev/null
+++ b/services/core/java/com/android/server/pm/KeySetHandle.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+    * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+     *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.os.Binder;
+
+public class KeySetHandle extends Binder {
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index c19951f..37bedf3 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import android.content.pm.KeySet;
 import android.content.pm.PackageParser;
 import android.os.Binder;
 import android.util.ArraySet;
@@ -52,7 +51,7 @@
     /** Sentinel value returned when public key is not found. */
     protected static final long PUBLIC_KEY_NOT_FOUND = -1;
 
-    private final LongSparseArray<KeySet> mKeySets;
+    private final LongSparseArray<KeySetHandle> mKeySets;
 
     private final LongSparseArray<PublicKey> mPublicKeys;
 
@@ -65,7 +64,7 @@
     private static long lastIssuedKeyId = 0;
 
     public KeySetManagerService(Map<String, PackageSetting> packages) {
-        mKeySets = new LongSparseArray<KeySet>();
+        mKeySets = new LongSparseArray<KeySetHandle>();
         mPublicKeys = new LongSparseArray<PublicKey>();
         mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
         mPackages = packages;
@@ -82,7 +81,7 @@
      *
      * Note that this can return true for multiple KeySets.
      */
-    public boolean packageIsSignedByLPr(String packageName, KeySet ks) {
+    public boolean packageIsSignedByLPr(String packageName, KeySetHandle ks) {
         PackageSetting pkg = mPackages.get(packageName);
         if (pkg == null) {
             throw new NullPointerException("Invalid package name");
@@ -91,16 +90,42 @@
             throw new NullPointerException("Package has no KeySet data");
         }
         long id = getIdByKeySetLPr(ks);
+        if (id == KEYSET_NOT_FOUND) {
+                return false;
+        }
         return pkg.keySetData.packageIsSignedBy(id);
     }
 
     /**
+     * Determine if a package is signed by the given KeySet.
+     *
+     * Returns false if the package was not signed by all the
+     * keys in the KeySet, or if the package was signed by keys
+     * not in the KeySet.
+     *
+     * Note that this can return only for one KeySet.
+     */
+    public boolean packageIsSignedByExactlyLPr(String packageName, KeySetHandle ks) {
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("Invalid package name");
+        }
+        if (pkg.keySetData == null
+            || pkg.keySetData.getProperSigningKeySet()
+            == PackageKeySetData.KEYSET_UNASSIGNED) {
+            throw new NullPointerException("Package has no KeySet data");
+        }
+        long id = getIdByKeySetLPr(ks);
+        return pkg.keySetData.getProperSigningKeySet() == id;
+    }
+
+    /**
      * This informs the system that the given package has defined a KeySet
      * in its manifest that a) contains the given keys and b) is named
      * alias by that package.
      */
     public void addDefinedKeySetToPackageLPw(String packageName,
-            Set<PublicKey> keys, String alias) {
+            ArraySet<PublicKey> keys, String alias) {
         if ((packageName == null) || (keys == null) || (alias == null)) {
             Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
             return;
@@ -110,7 +135,7 @@
             throw new NullPointerException("Unknown package");
         }
         // Add to KeySets, then to package
-        KeySet ks = addKeySetLPw(keys);
+        KeySetHandle ks = addKeySetLPw(keys);
         long id = getIdByKeySetLPr(ks);
         pkg.keySetData.addDefinedKeySet(id, alias);
     }
@@ -137,19 +162,18 @@
      * was signed by the provided KeySet.
      */
     public void addSigningKeySetToPackageLPw(String packageName,
-            Set<PublicKey> signingKeys) {
+            ArraySet<PublicKey> signingKeys) {
         if ((packageName == null) || (signingKeys == null)) {
             Slog.w(TAG, "Got null argument for a signing keyset, ignoring!");
             return;
         }
         // add the signing KeySet
-        KeySet ks = addKeySetLPw(signingKeys);
+        KeySetHandle ks = addKeySetLPw(signingKeys);
         long id = getIdByKeySetLPr(ks);
-        Set<Long> publicKeyIds = mKeySetMapping.get(id);
+        ArraySet<Long> publicKeyIds = mKeySetMapping.get(id);
         if (publicKeyIds == null) {
             throw new NullPointerException("Got invalid KeySet id");
         }
-
         // attach it to the package
         PackageSetting pkg = mPackages.get(packageName);
         if (pkg == null) {
@@ -160,7 +184,7 @@
         // KeySet id to the package's signing KeySets
         for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
             long keySetID = mKeySets.keyAt(keySetIndex);
-            Set<Long> definedKeys = mKeySetMapping.get(keySetID);
+            ArraySet<Long> definedKeys = mKeySetMapping.get(keySetID);
             if (publicKeyIds.containsAll(definedKeys)) {
                 pkg.keySetData.addSigningKeySet(keySetID);
             }
@@ -171,9 +195,9 @@
      * Fetches the stable identifier associated with the given KeySet. Returns
      * {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
      */
-    private long getIdByKeySetLPr(KeySet ks) {
+    private long getIdByKeySetLPr(KeySetHandle ks) {
         for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
-            KeySet value = mKeySets.valueAt(keySetIndex);
+            KeySetHandle value = mKeySets.valueAt(keySetIndex);
             if (ks.equals(value)) {
                 return mKeySets.keyAt(keySetIndex);
             }
@@ -187,25 +211,24 @@
      * Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
      * identify a {@link KeySet}.
      */
-    public KeySet getKeySetByIdLPr(long id) {
+    public KeySetHandle getKeySetByIdLPr(long id) {
         return mKeySets.get(id);
     }
 
     /**
-     * Fetches the {@link KeySet} that a given package refers to by the provided alias.
-     *
-     * @throws IllegalArgumentException if the package has no keyset data.
-     * @throws NullPointerException if the package is unknown.
+     * Fetches the {@link KeySetHandle} that a given package refers to by the
+     * provided alias. Returns null if the package is unknown or does not have a
+     * KeySet corresponding to that alias.
      */
-    public KeySet getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
+    public KeySetHandle getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
         PackageSetting p = mPackages.get(packageName);
-        if (p == null) {
-            throw new NullPointerException("Unknown package");
+        if (p == null || p.keySetData == null) {
+                return null;
         }
-        if (p.keySetData == null) {
-            throw new IllegalArgumentException("Package has no keySet data");
+        Long keySetId = p.keySetData.getAliases().get(alias);
+        if (keySetId == null) {
+            throw new IllegalArgumentException("Unknown KeySet alias: " + alias);
         }
-        long keySetId = p.keySetData.getAliases().get(alias);
         return mKeySets.get(keySetId);
     }
 
@@ -214,7 +237,7 @@
      * KeySet id.
      *
      * Returns {@code null} if the identifier doesn't
-     * identify a {@link KeySet}.
+     * identify a {@link KeySetHandle}.
      */
     public ArraySet<PublicKey> getPublicKeysFromKeySetLPr(long id) {
         if(mKeySetMapping.get(id) == null) {
@@ -228,36 +251,32 @@
     }
 
     /**
-     * Fetches all the known {@link KeySet KeySets} that signed the given
+     * Fetches the proper {@link KeySetHandle KeySet} that signed the given
      * package.
      *
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public Set<KeySet> getSigningKeySetsByPackageNameLPr(String packageName) {
-        Set<KeySet> signingKeySets = new ArraySet<KeySet>();
+    public KeySetHandle  getSigningKeySetByPackageNameLPr(String packageName) {
         PackageSetting p = mPackages.get(packageName);
-        if (p == null) {
-            throw new NullPointerException("Unknown package");
+        if (p == null
+            || p.keySetData == null
+            || p.keySetData.getProperSigningKeySet()
+            == PackageKeySetData.KEYSET_UNASSIGNED) {
+            return null;
         }
-        if (p.keySetData == null || p.keySetData.getSigningKeySets() == null) {
-            throw new IllegalArgumentException("Package has no keySet data");
-        }
-        for (long l : p.keySetData.getSigningKeySets()) {
-            signingKeySets.add(mKeySets.get(l));
-        }
-        return signingKeySets;
+        return mKeySets.get(p.keySetData.getProperSigningKeySet());
     }
 
     /**
-     * Fetches all the known {@link KeySet KeySets} that may upgrade the given
+     * Fetches all the known {@link KeySetHandle KeySets} that may upgrade the given
      * package.
      *
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public ArraySet<KeySet> getUpgradeKeySetsByPackageNameLPr(String packageName) {
-        ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
+    public ArraySet<KeySetHandle> getUpgradeKeySetsByPackageNameLPr(String packageName) {
+        ArraySet<KeySetHandle> upgradeKeySets = new ArraySet<KeySetHandle>();
         PackageSetting p = mPackages.get(packageName);
         if (p == null) {
             throw new NullPointerException("Unknown package");
@@ -287,7 +306,7 @@
      *
      * Throws if the provided set is {@code null}.
      */
-    private KeySet addKeySetLPw(Set<PublicKey> keys) {
+    private KeySetHandle addKeySetLPw(ArraySet<PublicKey> keys) {
         if (keys == null) {
             throw new NullPointerException("Provided keys cannot be null");
         }
@@ -305,7 +324,7 @@
         }
 
         // create the KeySet object
-        KeySet ks = new KeySet(new Binder());
+        KeySetHandle ks = new KeySetHandle();
         // get the first unoccupied slot in mKeySets
         long id = getFreeKeySetIDLPw();
         // add the KeySet object to it
@@ -318,7 +337,7 @@
             if (p.keySetData != null) {
                 long pProperSigning = p.keySetData.getProperSigningKeySet();
                 if (pProperSigning != PackageKeySetData.KEYSET_UNASSIGNED) {
-                    Set<Long> pSigningKeys = mKeySetMapping.get(pProperSigning);
+                    ArraySet<Long> pSigningKeys = mKeySetMapping.get(pProperSigning);
                     if (pSigningKeys.containsAll(addedKeyIds)) {
                         p.keySetData.addSigningKeySet(id);
                     }
@@ -353,7 +372,7 @@
      */
     private long getIdFromKeyIdsLPr(Set<Long> publicKeyIds) {
         for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
-            Set<Long> value = mKeySetMapping.valueAt(keyMapIndex);
+            ArraySet<Long> value = mKeySetMapping.valueAt(keyMapIndex);
             if (value.equals(publicKeyIds)) {
                 return mKeySetMapping.keyAt(keyMapIndex);
             }
@@ -582,7 +601,7 @@
         serializer.startTag(null, "keysets");
         for (int keySetIndex = 0; keySetIndex < mKeySetMapping.size(); keySetIndex++) {
             long id = mKeySetMapping.keyAt(keySetIndex);
-            Set<Long> keys = mKeySetMapping.valueAt(keySetIndex);
+            ArraySet<Long> keys = mKeySetMapping.valueAt(keySetIndex);
             serializer.startTag(null, "keyset");
             serializer.attribute(null, "identifier", Long.toString(id));
             for (long keyId : keys) {
@@ -662,7 +681,7 @@
             final String tagName = parser.getName();
             if (tagName.equals("keyset")) {
                 currentKeySetId = readIdentifierLPw(parser);
-                mKeySets.put(currentKeySetId, new KeySet(new Binder()));
+                mKeySets.put(currentKeySetId, new KeySetHandle());
                 mKeySetMapping.put(currentKeySetId, new ArraySet<Long>());
             } else if (tagName.equals("key-id")) {
                 long id = readIdentifierLPw(parser);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0eb922d..190e87c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -60,6 +60,7 @@
 
     // TODO: destroy sessions with old timestamps
     // TODO: remove outstanding sessions when installer package goes away
+    // TODO: notify listeners in other users when package has been installed there
 
     private final Context mContext;
     private final PackageManagerService mPm;
@@ -167,6 +168,10 @@
             params.installFlags |= INSTALL_REPLACE_EXISTING;
         }
 
+        if (params.mode == InstallSessionParams.MODE_INVALID) {
+            throw new IllegalArgumentException("Params must have valid mode set");
+        }
+
         // Sanity check that install could fit
         if (params.deltaSize > 0) {
             try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index c399fa2..31d9704 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -166,7 +166,7 @@
         info.installerPackageName = installerPackageName;
         info.progress = mProgress;
 
-        info.fullInstall = params.fullInstall;
+        info.mode = params.mode;
         info.packageName = params.packageName;
         info.icon = params.icon;
         info.title = params.title;
@@ -177,7 +177,8 @@
     @Override
     public void setClientProgress(int progress) {
         mClientProgress = progress;
-        mProgress = MathUtils.constrain((mClientProgress * 8 * 100) / (params.progressMax * 10), 0, 80);
+        mProgress = MathUtils.constrain(
+                (int) (((float) mClientProgress) / ((float) params.progressMax)) * 80, 0, 80);
         mCallback.onSessionProgress(this, mProgress);
     }
 
@@ -288,7 +289,7 @@
 
         // Inherit any packages and native libraries from existing install that
         // haven't been overridden.
-        if (!params.fullInstall) {
+        if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) {
             spliceExistingFilesIntoStage();
         }
 
@@ -382,7 +383,7 @@
         // currently relying on PMS to do this.
         // TODO: teach about compatible upgrade keysets.
 
-        if (params.fullInstall) {
+        if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) {
             // Full installs must include a base package
             if (!seenSplits.contains(null)) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 101ef92..0ad3a68 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);
             }
@@ -9331,14 +9347,18 @@
                 return false;
             } else {
                 final File beforeCodeFile = codeFile;
-                final File afterCodeFile = new File(mAppInstallDir,
-                        getNextCodePath(oldCodePath, pkg.packageName, null));
+                final File afterCodeFile = getNextCodePath(pkg.packageName);
 
                 Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
-                if (!beforeCodeFile.renameTo(afterCodeFile)) {
+                try {
+                    Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
+                } catch (ErrnoException e) {
+                    Slog.d(TAG, "Failed to rename", e);
                     return false;
                 }
+
                 if (!SELinux.restoreconRecursive(afterCodeFile)) {
+                    Slog.d(TAG, "Failed to restorecon");
                     return false;
                 }
 
@@ -9455,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.
@@ -9811,6 +9841,16 @@
         return prefix + idxStr;
     }
 
+    private File getNextCodePath(String packageName) {
+        int suffix = 1;
+        File result;
+        do {
+            result = new File(mAppInstallDir, packageName + "-" + suffix);
+            suffix++;
+        } while (result.exists());
+        return result;
+    }
+
     // Utility method used to ignore ADD/REMOVE events
     // by directory observer.
     private static boolean ignoreCodePath(String fullPathStr) {
@@ -9865,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;
@@ -9918,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);
         }
     }
 
@@ -9974,6 +10025,10 @@
                 perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
             }
         }
+
+        // Nuke any cached code
+        deleteCodeCacheDirsLI(pkgName);
+
         boolean sysPkg = (isSystemApp(oldPackage));
         if (sysPkg) {
             replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
@@ -10017,8 +10072,7 @@
                 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);
             }
         }
 
@@ -10121,7 +10175,7 @@
                 res.removedInfo.args = null;
             }
         }
-        
+
         // Successfully disabled the old package. Now proceed with re-installation
         res.returnCode = PackageManager.INSTALL_SUCCEEDED;
         pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -10149,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) {
@@ -10195,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);
                 }
@@ -10288,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;
         }
 
@@ -10304,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;
         }
 
@@ -13255,4 +13308,83 @@
         }
         return mUserNeedsBadging.valueAt(index);
     }
+
+    @Override
+    public KeySetHandle getKeySetByAlias(String packageName, String alias) {
+        if (packageName == null || alias == null) {
+            return null;
+        }
+        synchronized(mPackages) {
+            final PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+            if (pkg.applicationInfo.uid != Binder.getCallingUid()
+                    && Process.SYSTEM_UID != Binder.getCallingUid()) {
+                throw new SecurityException("May not access KeySets defined by"
+                        + " aliases in other applications.");
+            }
+            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            return ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias);
+        }
+    }
+
+    @Override
+    public KeySetHandle getSigningKeySet(String packageName) {
+        if (packageName == null) {
+            return null;
+        }
+        synchronized(mPackages) {
+            final PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+            if (pkg.applicationInfo.uid != Binder.getCallingUid()
+                    && Process.SYSTEM_UID != Binder.getCallingUid()) {
+                throw new SecurityException("May not access signing KeySet of other apps.");
+            }
+            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            return ksms.getSigningKeySetByPackageNameLPr(packageName);
+        }
+    }
+
+    @Override
+    public boolean isPackageSignedByKeySet(String packageName, IBinder ks) {
+        if (packageName == null || ks == null) {
+            return false;
+        }
+        synchronized(mPackages) {
+            final PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+            if (ks instanceof KeySetHandle) {
+                KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ks);
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isPackageSignedByKeySetExactly(String packageName, IBinder ks) {
+        if (packageName == null || ks == null) {
+            return false;
+        }
+        synchronized(mPackages) {
+            final PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+            if (ks instanceof KeySetHandle) {
+                KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ks);
+            }
+            return false;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 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/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a69dd30..ebad422 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -58,6 +58,8 @@
 import android.os.SystemService;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.os.Parcel;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
 import android.util.EventLog;
@@ -659,6 +661,7 @@
         if (mLowPowerModeEnabled != lowPowerModeEnabled) {
             mLowPowerModeEnabled = lowPowerModeEnabled;
             powerHintInternal(POWER_HINT_LOW_POWER_MODE, lowPowerModeEnabled ? 1 : 0);
+            setSurfaceFlingerLowPowerMode(lowPowerModeEnabled ? 1 : 0);
             mLowPowerModeEnabled = lowPowerModeEnabled;
             BackgroundThread.getHandler().post(new Runnable() {
                 @Override
@@ -2094,6 +2097,21 @@
         nativeSendPowerHint(hintId, data);
     }
 
+    private static void setSurfaceFlingerLowPowerMode(int enabled) {
+        try {
+            final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+            if (flinger != null) {
+                final Parcel data = Parcel.obtain();
+                data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                data.writeInt(enabled);
+                flinger.transact(1016, data, null, 0);
+                data.recycle();
+            }
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Failed to reduce refresh rate", ex);
+        }
+    }
+
     /**
      * Low-level function turn the device off immediately, without trying
      * to be clean.  Most people should use {@link ShutdownThread} for a clean shutdown.
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index efe543b..8f237db 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -16,7 +16,12 @@
 
 package com.android.server.tv;
 
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
 import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiHotplugEvent;
 import android.media.AudioDevicePort;
 import android.media.AudioManager;
 import android.media.AudioPatch;
@@ -25,14 +30,22 @@
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.TvInputHardwareInfo;
+import android.media.tv.TvInputInfo;
 import android.media.tv.TvStreamConfig;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
 import android.view.Surface;
 
+import com.android.server.SystemService;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -46,23 +59,42 @@
  *
  * @hide
  */
-class TvInputHardwareManager implements TvInputHal.Callback {
+class TvInputHardwareManager
+        implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener {
     private static final String TAG = TvInputHardwareManager.class.getSimpleName();
     private final TvInputHal mHal = new TvInputHal(this);
     private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
     private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
     private final Context mContext;
+    private final TvInputManagerService.Client mClient;
     private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
     private final AudioManager mAudioManager;
+    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
+    // TODO: Should handle INACTIVE case.
+    private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
+
+    // Calls to mClient should happen here.
+    private final HandlerThread mHandlerThread = new HandlerThread(TAG);
+    private final Handler mHandler;
 
     private final Object mLock = new Object();
 
-    public TvInputHardwareManager(Context context) {
+    public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
         mContext = context;
+        mClient = client;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
-        // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
         mHal.init();
+
+        mHandlerThread.start();
+        mHandler = new ClientHandler(mHandlerThread.getLooper());
+    }
+
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            HdmiControlManager hdmiControlManager =
+                    (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
+            hdmiControlManager.addHotplugEventListener(this);
+        }
     }
 
     @Override
@@ -80,7 +112,7 @@
     private void buildInfoListLocked() {
         mInfoList.clear();
         for (int i = 0; i < mConnections.size(); ++i) {
-            mInfoList.add(mConnections.valueAt(i).getInfoLocked());
+            mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
         }
     }
 
@@ -92,7 +124,7 @@
                 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
                 return;
             }
-            connection.resetLocked(null, null, null, null);
+            connection.resetLocked(null, null, null, null, null);
             mConnections.remove(deviceId);
             buildInfoListLocked();
             // TODO: notify if necessary
@@ -136,6 +168,37 @@
         return false;
     }
 
+    private int convertConnectedToState(boolean connected) {
+        if (connected) {
+            return INPUT_STATE_CONNECTED;
+        } else {
+            return INPUT_STATE_DISCONNECTED;
+        }
+    }
+
+    public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+        if (info.getType() == TvInputInfo.TYPE_VIRTUAL) {
+            throw new IllegalArgumentException("info (" + info + ") has virtual type.");
+        }
+        synchronized (mLock) {
+            if (mTvInputInfoMap.indexOfKey(deviceId) >= 0) {
+                Slog.w(TAG, "Trying to override previous registration: old = "
+                        + mTvInputInfoMap.get(deviceId) + ":" + deviceId + ", new = "
+                        + info + ":" + deviceId);
+            }
+            mTvInputInfoMap.put(deviceId, info);
+
+            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
+                String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
+                if (inputId != null && inputId.equals(info.getId())) {
+                    mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
+                            inputId).sendToTarget();
+                }
+            }
+        }
+    }
+
     /**
      * Create a TvInputHardware object with a specific deviceId. One service at a time can access
      * the object, and if more than one process attempts to create hardware with the same deviceId,
@@ -143,7 +206,7 @@
      * release is notified via ITvInputHardwareCallback.onReleased().
      */
     public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
-            int callingUid, int resolvedUserId) {
+            TvInputInfo info, int callingUid, int resolvedUserId) {
         if (callback == null) {
             throw new NullPointerException();
         }
@@ -154,14 +217,15 @@
                 return null;
             }
             if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
-                TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
+                TvInputHardwareImpl hardware =
+                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
                 try {
                     callback.asBinder().linkToDeath(connection, 0);
                 } catch (RemoteException e) {
                     hardware.release();
                     return null;
                 }
-                connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
+                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
             }
             return connection.getHardwareLocked();
         }
@@ -182,26 +246,55 @@
                     || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
                 return;
             }
-            connection.resetLocked(null, null, null, null);
+            connection.resetLocked(null, null, null, null, null);
+        }
+    }
+
+    private String findInputIdForHdmiPortLocked(int port) {
+        for (TvInputHardwareInfo hardwareInfo : mInfoList) {
+            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
+                    && hardwareInfo.getHdmiPortId() == port) {
+                TvInputInfo info = mTvInputInfoMap.get(hardwareInfo.getDeviceId());
+                return (info == null) ? null : info.getId();
+            }
+        }
+        return null;
+    }
+
+    // HdmiControlManager.HotplugEventListener implementation.
+
+    @Override
+    public void onReceived(HdmiHotplugEvent event) {
+        String inputId = null;
+
+        synchronized (mLock) {
+            mHdmiStateMap.put(event.getPort(), event.isConnected());
+            inputId = findInputIdForHdmiPortLocked(event.getPort());
+            if (inputId == null) {
+                return;
+            }
+            mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+                    convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
         }
     }
 
     private class Connection implements IBinder.DeathRecipient {
-        private final TvInputHardwareInfo mInfo;
+        private final TvInputHardwareInfo mHardwareInfo;
+        private TvInputInfo mInfo;
         private TvInputHardwareImpl mHardware = null;
         private ITvInputHardwareCallback mCallback;
         private TvStreamConfig[] mConfigs = null;
         private Integer mCallingUid = null;
         private Integer mResolvedUserId = null;
 
-        public Connection(TvInputHardwareInfo info) {
-            mInfo = info;
+        public Connection(TvInputHardwareInfo hardwareInfo) {
+            mHardwareInfo = hardwareInfo;
         }
 
         // *Locked methods assume TvInputHardwareManager.mLock is held.
 
-        public void resetLocked(TvInputHardwareImpl hardware,
-                ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
+        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
+                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
             if (mHardware != null) {
                 try {
                     mCallback.onReleased();
@@ -212,6 +305,7 @@
             }
             mHardware = hardware;
             mCallback = callback;
+            mInfo = info;
             mCallingUid = callingUid;
             mResolvedUserId = resolvedUserId;
 
@@ -228,7 +322,11 @@
             mConfigs = configs;
         }
 
-        public TvInputHardwareInfo getInfoLocked() {
+        public TvInputHardwareInfo getHardwareInfoLocked() {
+            return mHardwareInfo;
+        }
+
+        public TvInputInfo getInfoLocked() {
             return mInfo;
         }
 
@@ -255,7 +353,7 @@
         @Override
         public void binderDied() {
             synchronized (mLock) {
-                resetLocked(null, null, null, null);
+                resetLocked(null, null, null, null, null);
             }
         }
     }
@@ -403,4 +501,28 @@
             return false;
         }
     }
+
+    private class ClientHandler extends Handler {
+        private static final int DO_SET_AVAILABLE = 1;
+
+        ClientHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public final void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DO_SET_AVAILABLE: {
+                    String inputId = (String) msg.obj;
+                    int state = msg.arg1;
+                    mClient.setState(inputId, state);
+                    break;
+                }
+                default: {
+                    Slog.w(TAG, "Unhandled message: " + msg);
+                    break;
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5e95af4..20fdefa 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.tv;
 
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -38,6 +41,7 @@
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.ITvInputManager;
+import android.media.tv.ITvInputManagerCallback;
 import android.media.tv.ITvInputService;
 import android.media.tv.ITvInputServiceCallback;
 import android.media.tv.ITvInputSession;
@@ -109,7 +113,7 @@
         mContentResolver = context.getContentResolver();
         mLogHandler = new LogHandler(IoThread.get().getLooper());
 
-        mTvInputHardwareManager = new TvInputHardwareManager(context);
+        mTvInputHardwareManager = new TvInputHardwareManager(context, new Client());
 
         synchronized (mLock) {
             mUserStates.put(mCurrentUserId, new UserState());
@@ -129,6 +133,7 @@
                 buildTvInputListLocked(mCurrentUserId);
             }
         }
+        mTvInputHardwareManager.onBootPhase(phase);
     }
 
     private void registerBroadcastReceivers() {
@@ -144,7 +149,7 @@
             public void onPackageRemoved(String packageName, int uid) {
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(mCurrentUserId);
-                    if (!userState.packageList.contains(packageName)) {
+                    if (!userState.packageSet.contains(packageName)) {
                         // Not a TV input package.
                         return;
                     }
@@ -198,8 +203,11 @@
 
     private void buildTvInputListLocked(int userId) {
         UserState userState = getUserStateLocked(userId);
-        userState.inputMap.clear();
-        userState.packageList.clear();
+
+        Map<String, TvInputState> oldInputMap = userState.inputMap;
+        userState.inputMap = new HashMap<String, TvInputState>();
+
+        userState.packageSet.clear();
 
         if (DEBUG) Slog.d(TAG, "buildTvInputList");
         PackageManager pm = mContext.getPackageManager();
@@ -216,8 +224,13 @@
             try {
                 TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
                 if (DEBUG) Slog.d(TAG, "add " + info.getId());
-                userState.inputMap.put(info.getId(), info);
-                userState.packageList.add(si.packageName);
+                TvInputState state = oldInputMap.get(info.getId());
+                if (state == null) {
+                    state = new TvInputState();
+                }
+                userState.inputMap.put(info.getId(), state);
+                state.mInfo = info;
+                userState.packageSet.add(si.packageName);
 
                 // Reconnect the service if existing input is updated.
                 updateServiceConnectionLocked(info.getId(), userId);
@@ -225,6 +238,7 @@
                 Slog.e(TAG, "Can't load TV input " + si.name, e);
             }
         }
+        oldInputMap.clear();
     }
 
     private void switchUser(int userId) {
@@ -359,7 +373,7 @@
             }
 
             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
-                    userState.inputMap.get(inputId).getComponent());
+                    userState.inputMap.get(inputId).mInfo.getComponent());
             // Binding service may fail if the service is updating.
             // In that case, the connection will be revived in buildTvInputListLocked called by
             // onSomePackagesChanged.
@@ -588,7 +602,7 @@
         updateServiceConnectionLocked(sessionState.mInputId, userId);
     }
 
-    private void unregisterCallbackInternalLocked(IBinder clientToken, String inputId,
+    private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
             int userId) {
         UserState userState = getUserStateLocked(userId);
         ClientState clientState = userState.clientStateMap.get(clientToken);
@@ -623,14 +637,43 @@
         }
     }
 
-    private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
-        for (IBinder clientToken : serviceState.mClientTokens) {
-            try {
-                ITvInputClient.Stub.asInterface(clientToken).onAvailabilityChanged(
-                        serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "error in onAvailabilityChanged", e);
+    private void notifyStateChangedLocked(UserState userState, String inputId,
+            int state, ITvInputManagerCallback targetCallback) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
+                    + "; state = " + state);
+        }
+        if (targetCallback == null) {
+            for (ITvInputManagerCallback callback : userState.callbackSet) {
+                try {
+                    callback.onInputStateChanged(inputId, state);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to report state change to callback.");
+                }
             }
+        } else {
+            try {
+                targetCallback.onInputStateChanged(inputId, state);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to report state change to callback.");
+            }
+        }
+    }
+
+    private void setStateLocked(String inputId, int state, int userId) {
+        UserState userState = getUserStateLocked(userId);
+        TvInputState inputState = userState.inputMap.get(inputId);
+        ServiceState serviceState = userState.serviceStateMap.get(inputId);
+        int oldState = inputState.mState;
+        inputState.mState = state;
+        boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
+                && serviceState.mSessionTokens.isEmpty();
+        if (serviceState != null && serviceState.mService == null && !isStateEmpty) {
+            // We don't notify state change while reconnecting. It should remain disconnected.
+            return;
+        }
+        if (oldState != state) {
+            notifyStateChangedLocked(userState, inputId, state, null);
         }
     }
 
@@ -643,80 +686,29 @@
             try {
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
-        public boolean getAvailability(final ITvInputClient client, final String inputId,
-                int userId) {
-            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, "getAvailability");
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                synchronized (mLock) {
-                    UserState userState = getUserStateLocked(resolvedUserId);
-                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
-                    if (serviceState != null) {
-                        // We already know the status of this input service. Return the cached
-                        // status.
-                        return serviceState.mAvailable;
+                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
+                    for (TvInputState state : userState.inputMap.values()) {
+                        inputList.add(state.mInfo);
                     }
+                    return inputList;
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
-            // STOPSHIP: Redesign the API around the availability change. For now, the service
-            // will be always available.
-            return true;
         }
 
         @Override
-        public void registerCallback(final ITvInputClient client, final String inputId,
-                int userId) {
+        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "registerCallback");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    // Create a new service callback and add it to the callback map of the current
-                    // service.
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
-                    if (serviceState == null) {
-                        serviceState = new ServiceState(
-                                userState.inputMap.get(inputId), resolvedUserId);
-                        userState.serviceStateMap.put(inputId, serviceState);
-                    }
-                    IBinder clientToken = client.asBinder();
-                    if (!serviceState.mClientTokens.contains(clientToken)) {
-                        serviceState.mClientTokens.add(clientToken);
-                    }
-
-                    ClientState clientState = userState.clientStateMap.get(clientToken);
-                    if (clientState == null) {
-                        clientState = createClientStateLocked(clientToken, resolvedUserId);
-                    }
-                    if (!clientState.mInputIds.contains(inputId)) {
-                        clientState.mInputIds.add(inputId);
-                    }
-
-                    if (serviceState.mService != null) {
-                        if (serviceState.mCallback != null) {
-                            // We already handled.
-                            return;
-                        }
-                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
-                        try {
-                            serviceState.mService.registerCallback(serviceState.mCallback);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in registerCallback", e);
-                        }
-                    } else {
-                        updateServiceConnectionLocked(inputId, resolvedUserId);
+                    userState.callbackSet.add(callback);
+                    for (TvInputState state : userState.inputMap.values()) {
+                        notifyStateChangedLocked(userState, state.mInfo.getId(),
+                                state.mState, callback);
                     }
                 }
             } finally {
@@ -725,13 +717,14 @@
         }
 
         @Override
-        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
+        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "unregisterCallback");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    unregisterCallbackInternalLocked(client.asBinder(), inputId, resolvedUserId);
+                    UserState userState = getUserStateLocked(resolvedUserId);
+                    userState.callbackSet.remove(callback);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -751,7 +744,7 @@
                     ServiceState serviceState = userState.serviceStateMap.get(inputId);
                     if (serviceState == null) {
                         serviceState = new ServiceState(
-                                userState.inputMap.get(inputId), resolvedUserId);
+                                userState.inputMap.get(inputId).mInfo, resolvedUserId);
                         userState.serviceStateMap.put(inputId, serviceState);
                     }
                     // Send a null token immediately while reconnecting.
@@ -868,7 +861,7 @@
                         }
 
                         // Create a log entry and fill it later.
-                        String packageName = userState.inputMap.get(sessionState.mInputId)
+                        String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
                                 .getServiceInfo().packageName;
                         ContentValues values = new ContentValues();
                         values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
@@ -1017,8 +1010,7 @@
 
         @Override
         public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return null;
             }
@@ -1032,10 +1024,25 @@
         }
 
         @Override
+        public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public ITvInputHardware acquireTvInputHardware(int deviceId,
-                ITvInputHardwareCallback callback, int userId) throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
+                throws RemoteException {
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return null;
             }
@@ -1046,7 +1053,7 @@
                     userId, "acquireTvInputHardware");
             try {
                 return mTvInputHardwareManager.acquireHardware(
-                        deviceId, callback, callingUid, resolvedUserId);
+                        deviceId, callback, info, callingUid, resolvedUserId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -1055,8 +1062,7 @@
         @Override
         public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
                 throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return;
             }
@@ -1099,16 +1105,16 @@
                     pw.println("UserState (" + userId + "):");
                     pw.increaseIndent();
 
-                    pw.println("inputMap: inputId -> TvInputInfo");
+                    pw.println("inputMap: inputId -> TvInputState");
                     pw.increaseIndent();
-                    for (TvInputInfo info : userState.inputMap.values()) {
-                        pw.println(info.toString());
+                    for (TvInputState state : userState.inputMap.values()) {
+                        pw.println(state.toString());
                     }
                     pw.decreaseIndent();
 
-                    pw.println("packageList:");
+                    pw.println("packageSet:");
                     pw.increaseIndent();
-                    for (String packageName : userState.packageList) {
+                    for (String packageName : userState.packageSet) {
                         pw.println(packageName);
                     }
                     pw.decreaseIndent();
@@ -1169,7 +1175,6 @@
                         pw.println("mService: " + service.mService);
                         pw.println("mCallback: " + service.mCallback);
                         pw.println("mBound: " + service.mBound);
-                        pw.println("mAvailable: " + service.mAvailable);
                         pw.println("mReconnecting: " + service.mReconnecting);
 
                         pw.decreaseIndent();
@@ -1196,18 +1201,38 @@
                     }
                     pw.decreaseIndent();
 
+                    pw.println("callbackSet:");
+                    pw.increaseIndent();
+                    for (ITvInputManagerCallback callback : userState.callbackSet) {
+                        pw.println(callback.toString());
+                    }
+                    pw.decreaseIndent();
+
                     pw.decreaseIndent();
                 }
             }
         }
     }
 
-    private static final class UserState {
-        // A mapping from the TV input id to its TvInputInfo.
-        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
+    private static final class TvInputState {
+        // A TvInputInfo object which represents the TV input.
+        private TvInputInfo mInfo;
 
-        // A list of all TV input packages.
-        private final Set<String> packageList = new HashSet<String>();
+        // The state of TV input. Connected by default.
+        private int mState = INPUT_STATE_CONNECTED;
+
+        @Override
+        public String toString() {
+            return "mInfo: " + mInfo + "; mState: " + mState;
+        }
+    }
+
+    private static final class UserState {
+        // A mapping from the TV input id to its TvInputState.
+        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
+
+        // A set of all TV input packages.
+        private final Set<String> packageSet = new HashSet<String>();
 
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> clientStateMap =
@@ -1220,6 +1245,10 @@
         // A mapping from the token of a TV input session to its state.
         private final Map<IBinder, SessionState> sessionStateMap =
                 new HashMap<IBinder, SessionState>();
+
+        // A set of callbacks.
+        private final Set<ITvInputManagerCallback> callbackSet =
+                new HashSet<ITvInputManagerCallback>();
     }
 
     private final class ClientState implements IBinder.DeathRecipient {
@@ -1243,7 +1272,7 @@
             synchronized (mLock) {
                 UserState userState = getUserStateLocked(mUserId);
                 // DO NOT remove the client state of clientStateMap in this method. It will be
-                // removed in releaseSessionLocked() or unregisterCallbackInternalLocked().
+                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
                 ClientState clientState = userState.clientStateMap.get(mClientToken);
                 if (clientState != null) {
                     while (clientState.mSessionTokens.size() > 0) {
@@ -1251,7 +1280,7 @@
                                 clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
                     }
                     while (clientState.mInputIds.size() > 0) {
-                        unregisterCallbackInternalLocked(
+                        unregisterClientInternalLocked(
                                 mClientToken, clientState.mInputIds.get(0), mUserId);
                     }
                 }
@@ -1269,7 +1298,6 @@
         private ITvInputService mService;
         private ServiceCallback mCallback;
         private boolean mBound;
-        private boolean mAvailable;
         private boolean mReconnecting;
 
         private ServiceState(TvInputInfo inputInfo, int userId) {
@@ -1325,16 +1353,18 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            String inputId = mTvInputInfo.getId();
             if (DEBUG) {
-                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
+                Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
             }
             synchronized (mLock) {
-                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
+                UserState userState = getUserStateLocked(mUserId);
+                ServiceState serviceState = userState.serviceStateMap.get(inputId);
                 serviceState.mService = ITvInputService.Stub.asInterface(service);
 
                 // Register a callback, if we need to.
                 if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
-                    serviceState.mCallback = new ServiceCallback(mUserId);
+                    serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
                     try {
                         serviceState.mService.registerCallback(serviceState.mCallback);
                     } catch (RemoteException e) {
@@ -1346,6 +1376,12 @@
                 for (IBinder sessionToken : serviceState.mSessionTokens) {
                     createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
                 }
+
+                TvInputState inputState = userState.inputMap.get(inputId);
+                if (inputState != null && inputState.mState != INPUT_STATE_DISCONNECTED) {
+                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+                            inputState.mState, null);
+                }
             }
         }
 
@@ -1377,10 +1413,8 @@
                         }
                     }
 
-                    if (serviceState.mAvailable) {
-                        serviceState.mAvailable = false;
-                        broadcastServiceAvailabilityChangedLocked(serviceState);
-                    }
+                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+                            INPUT_STATE_DISCONNECTED, null);
                     updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
                 }
             }
@@ -1388,24 +1422,22 @@
     }
 
     private final class ServiceCallback extends ITvInputServiceCallback.Stub {
+        private final String mInputId;
         private final int mUserId;
 
-        ServiceCallback(int userId) {
+        ServiceCallback(String inputId, int userId) {
+            mInputId = inputId;
             mUserId = userId;
         }
 
         @Override
-        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+        public void onInputStateChanged(int state) {
             if (DEBUG) {
-                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
-                        + isAvailable + ")");
+                Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
+                        + state + ")");
             }
             synchronized (mLock) {
-                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
-                if (serviceState.mAvailable != isAvailable) {
-                    serviceState.mAvailable = isAvailable;
-                    broadcastServiceAvailabilityChangedLocked(serviceState);
-                }
+                setStateLocked(mInputId, state, mUserId);
             }
         }
     }
@@ -1553,4 +1585,12 @@
             mContentResolver.update(uri, values, null, null);
         }
     }
+
+    final class Client {
+        public void setState(String inputId, int state) {
+            synchronized (mLock) {
+                setStateLocked(inputId, state, mCurrentUserId);
+            }
+        }
+    }
 }
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..874e105e 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 8387b65..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;
     }
@@ -717,10 +715,10 @@
             float top = w.mFrame.top + w.mYOffset;
 
             // Adjust for surface insets.
-            width += attrs.shadowInsets.left + attrs.shadowInsets.right;
-            height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
-            left -= attrs.shadowInsets.left;
-            top -= attrs.shadowInsets.top;
+            width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
+            height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
+            left -= attrs.surfaceInsets.left;
+            top -= attrs.surfaceInsets.top;
 
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG, "Creating surface in session "
@@ -1140,19 +1138,12 @@
 
     void applyDecorRect(final Rect decorRect) {
         final WindowState w = mWin;
-        int width = w.mFrame.width();
-        int height = w.mFrame.height();
+        final int width = w.mFrame.width();
+        final int height = w.mFrame.height();
 
         // Compute the offset of the window in relation to the decor rect.
-        int left = w.mXOffset + w.mFrame.left;
-        int top = w.mYOffset + w.mFrame.top;
-
-        // Adjust for surface insets.
-        final WindowManager.LayoutParams attrs = w.mAttrs;
-        width += attrs.shadowInsets.left + attrs.shadowInsets.right;
-        height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
-        left -= attrs.shadowInsets.left;
-        top -= attrs.shadowInsets.top;
+        final int left = w.mXOffset + w.mFrame.left;
+        final int top = w.mYOffset + w.mFrame.top;
 
         // Initialize the decor rect to the entire frame.
         w.mSystemDecorRect.set(0, 0, width, height);
@@ -1182,7 +1173,6 @@
         if (displayContent == null) {
             return;
         }
-        DisplayInfo displayInfo = displayContent.getDisplayInfo();
 
         // Need to recompute a new system decor rect each time.
         if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
@@ -1192,6 +1182,7 @@
         } else if (!w.isDefaultDisplay()) {
             // On a different display there is no system decor.  Crop the window
             // by the screen boundaries.
+            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
             w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
             w.mSystemDecorRect.intersect(-w.mCompatFrame.left, -w.mCompatFrame.top,
                     displayInfo.logicalWidth - w.mCompatFrame.left,
@@ -1202,44 +1193,52 @@
             // windows need to be cropped by the screen, so they don't cover
             // the universe background.
             if (mAnimator.mUniverseBackground == null) {
-                w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(),
-                        w.mCompatFrame.height());
+                w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
             } else {
                 applyDecorRect(mService.mScreenRect);
             }
         } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
                 || w.mDecorFrame.isEmpty()) {
             // The universe background isn't cropped, nor windows without policy decor.
-            w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(),
-                    w.mCompatFrame.height());
+            w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
         } else {
             // Crop to the system decor specified by policy.
             applyDecorRect(w.mDecorFrame);
         }
 
-        // By default, the clip rect is the system decor rect
-        Rect clipRect = w.mSystemDecorRect;
-        if (mHasClipRect) {
+        // By default, the clip rect is the system decor.
+        final Rect clipRect = mTmpClipRect;
+        clipRect.set(w.mSystemDecorRect);
 
-            // If we have an animated clip rect, intersect it with the system decor rect
-            // NOTE: We are adding a temporary workaround due to the status bar not always reporting
-            // the correct system decor rect.  In such cases, we take into account the specified
-            // content insets as well.
-            int offsetTop = Math.max(w.mSystemDecorRect.top, w.mContentInsets.top);
-            mTmpClipRect.set(w.mSystemDecorRect);
-            // Don't apply the workaround to apps explicitly requesting fullscreen layout.
+        // Expand the clip rect for surface insets.
+        final WindowManager.LayoutParams attrs = w.mAttrs;
+        clipRect.left -= attrs.surfaceInsets.left;
+        clipRect.top -= attrs.surfaceInsets.top;
+        clipRect.right += attrs.surfaceInsets.right;
+        clipRect.bottom += attrs.surfaceInsets.bottom;
+
+        // If we have an animated clip rect, intersect it with the clip rect.
+        if (mHasClipRect) {
+            // NOTE: We are adding a temporary workaround due to the status bar
+            // not always reporting the correct system decor rect. In such
+            // cases, we take into account the specified content insets as well.
             if ((w.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN)
                     == SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) {
-                mTmpClipRect.intersect(mClipRect);
+                // Don't apply the workaround to apps explicitly requesting
+                // fullscreen layout.
+                clipRect.intersect(mClipRect);
             } else {
-                mTmpClipRect.offset(0, -offsetTop);
-                mTmpClipRect.intersect(mClipRect);
-                mTmpClipRect.offset(0, offsetTop);
+                final int offsetTop = Math.max(clipRect.top, w.mContentInsets.top);
+                clipRect.offset(0, -offsetTop);
+                clipRect.intersect(mClipRect);
+                clipRect.offset(0, offsetTop);
             }
-            clipRect = mTmpClipRect;
-
         }
 
+        // The clip rect was generated assuming (0,0) as the window origin,
+        // so we need to translate to match the actual surface coordinates.
+        clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
+
         if (!clipRect.equals(mLastClipRect)) {
             mLastClipRect.set(clipRect);
             try {
@@ -1285,10 +1284,10 @@
 
         // Adjust for surface insets.
         final LayoutParams attrs = w.getAttrs();
-        width += attrs.shadowInsets.left + attrs.shadowInsets.right;
-        height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
-        left -= attrs.shadowInsets.left;
-        top -= attrs.shadowInsets.top;
+        width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
+        height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
+        left -= attrs.surfaceInsets.left;
+        top -= attrs.surfaceInsets.top;
 
         final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top;
         if (surfaceMoved) {
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/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 5233297..c0923ca 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -656,14 +656,13 @@
                     }
                     break;
                 case MSG_USER_SWITCHED: {
-                    mCurrentUser = msg.arg1;
-
                     UserManager userManager =
                             (UserManager) mContext.getSystemService(Context.USER_SERVICE);
                     if (userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
                         Slog.v(TAG, "Switched to user with DISALLOW_USB_FILE_TRANSFER restriction;"
                                 + " disabling USB.");
                         setUsbConfig("none");
+                        mCurrentUser = msg.arg1;
                         break;
                     }
 
@@ -675,6 +674,7 @@
                         setUsbConfig("none");
                         setUsbConfig(mCurrentFunctions);
                     }
+                    mCurrentUser = msg.arg1;
                     break;
                 }
             }
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
new file mode 100644
index 0000000..9f38d0d
--- /dev/null
+++ b/telecomm/java/android/telecomm/Call.java
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents an ongoing phone call that the in-call app should present to the user.
+ */
+public final class Call {
+    /**
+     * The state of a {@code Call} when newly created.
+     */
+    public static final int STATE_NEW = 0;
+
+    /**
+     * The state of an outgoing {@code Call} when dialing the remote number, but not yet connected.
+     */
+    public static final int STATE_DIALING = 1;
+
+    /**
+     * The state of an incoming {@code Call} when ringing locally, but not yet connected.
+     */
+    public static final int STATE_RINGING = 2;
+
+    /**
+     * The state of a {@code Call} when in a holding state.
+     */
+    public static final int STATE_HOLDING = 3;
+
+    /**
+     * The state of a {@code Call} when actively supporting conversation.
+     */
+    public static final int STATE_ACTIVE = 4;
+
+    /**
+     * The state of a {@code Call} when no further voice or other communication is being
+     * transmitted, the remote side has been or will inevitably be informed that the {@code Call}
+     * is no longer active, and the local data transport has or inevitably will release resources
+     * associated with this {@code Call}.
+     */
+    public static final int STATE_DISCONNECTED = 7;
+
+    public static class Details {
+        private final Uri mHandle;
+        private final int mHandlePresentation;
+        private final String mCallerDisplayName;
+        private final int mCallerDisplayNamePresentation;
+        private final PhoneAccount mAccount;
+        private final int mCapabilities;
+        private final int mDisconnectCauseCode;
+        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
+         * connected.
+         */
+        public Uri getHandle() {
+            return mHandle;
+        }
+
+        /**
+         * @return The presentation requirements for the handle. See
+         * {@link android.telecomm.CallPropertyPresentation} for valid values.
+         */
+        public int getHandlePresentation() {
+            return mHandlePresentation;
+        }
+
+        /**
+         * @return The display name for the caller.
+         */
+        public String getCallerDisplayName() {
+            return mCallerDisplayName;
+        }
+
+        /**
+         * @return The presentation requirements for the caller display name. See
+         * {@link android.telecomm.CallPropertyPresentation} for valid values.
+         */
+        public int getCallerDisplayNamePresentation() {
+            return mCallerDisplayNamePresentation;
+        }
+
+        /**
+         * @return The {@code PhoneAccount} whereby the {@code Call} is currently being routed.
+         */
+        public PhoneAccount getAccount() {
+            return mAccount;
+        }
+
+        /**
+         * @return A bitmask of the capabilities of the {@code Call}, as defined in
+         *         {@link CallCapabilities}.
+         */
+        public int getCapabilities() {
+            return mCapabilities;
+        }
+
+        /**
+         * @return For a {@link #STATE_DISCONNECTED} {@code Call}, the disconnect cause expressed
+         * as a code chosen from among those declared in {@link DisconnectCause}.
+         */
+        public int getDisconnectCauseCode() {
+            return mDisconnectCauseCode;
+        }
+
+        /**
+         * @return For a {@link #STATE_DISCONNECTED} {@code Call}, an optional reason for
+         * disconnection expressed as a free text message.
+         */
+        public String getDisconnectCauseMsg() {
+            return mDisconnectCauseMsg;
+        }
+
+        /**
+         * @return The time the {@code Call} has been connected. This information is updated
+         * periodically, but user interfaces should not rely on this to display any "call time
+         * clock".
+         */
+        public long getConnectTimeMillis() {
+            return mConnectTimeMillis;
+        }
+
+        /**
+         * @return Information about any calling gateway the {@code Call} may be using.
+         */
+        public GatewayInfo getGatewayInfo() {
+            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) {
+                Details d = (Details) o;
+                return
+                        Objects.equals(mHandle, d.mHandle) &&
+                        Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
+                        Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
+                        Objects.equals(mCallerDisplayNamePresentation,
+                                d.mCallerDisplayNamePresentation) &&
+                        Objects.equals(mAccount, d.mAccount) &&
+                        Objects.equals(mCapabilities, d.mCapabilities) &&
+                        Objects.equals(mDisconnectCauseCode, d.mDisconnectCauseCode) &&
+                        Objects.equals(mDisconnectCauseMsg, d.mDisconnectCauseMsg) &&
+                        Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
+                        Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
+                        Objects.equals(mVideoState, d.mVideoState);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return
+                    Objects.hashCode(mHandle) +
+                    Objects.hashCode(mHandlePresentation) +
+                    Objects.hashCode(mCallerDisplayName) +
+                    Objects.hashCode(mCallerDisplayNamePresentation) +
+                    Objects.hashCode(mAccount) +
+                    Objects.hashCode(mCapabilities) +
+                    Objects.hashCode(mDisconnectCauseCode) +
+                    Objects.hashCode(mDisconnectCauseMsg) +
+                    Objects.hashCode(mConnectTimeMillis) +
+                    Objects.hashCode(mGatewayInfo) +
+                    Objects.hashCode(mVideoState);
+        }
+
+        /** {@hide} */
+        public Details(
+                Uri handle,
+                int handlePresentation,
+                String callerDisplayName,
+                int callerDisplayNamePresentation,
+                PhoneAccount account,
+                int capabilities,
+                int disconnectCauseCode,
+                String disconnectCauseMsg,
+                long connectTimeMillis,
+                GatewayInfo gatewayInfo,
+                int videoState) {
+            mHandle = handle;
+            mHandlePresentation = handlePresentation;
+            mCallerDisplayName = callerDisplayName;
+            mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+            mAccount = account;
+            mCapabilities = capabilities;
+            mDisconnectCauseCode = disconnectCauseCode;
+            mDisconnectCauseMsg = disconnectCauseMsg;
+            mConnectTimeMillis = connectTimeMillis;
+            mGatewayInfo = gatewayInfo;
+            mVideoState = videoState;
+        }
+    }
+
+    public static abstract class Listener {
+        /**
+         * Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
+         *
+         * TODO(ihab): Provide previous state also?
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param state The new state of the {@code Call}.
+         */
+        public void onStateChanged(Call call, int state) {}
+
+        /**
+         * Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param parent The new parent of the {@code Call}.
+         */
+        public void onParentChanged(Call call, Call parent) {}
+
+        /**
+         * Invoked when the children of this {@code Call} have changed. See {@link #getChildren()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param children The new children of the {@code Call}.
+         */
+        public void onChildrenChanged(Call call, List<Call> children) {}
+
+        /**
+         * Invoked when the details of this {@code Call} have changed. See {@link #getDetails()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param details A {@code Details} object describing the {@code Call}.
+         */
+        public void onDetailsChanged(Call call, Details details) {}
+
+        /**
+         * Invoked when the text messages that can be used as responses to the incoming
+         * {@code Call} are loaded from the relevant database.
+         * See {@link #getCannedTextResponses()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param cannedTextResponses The text messages useable as responses.
+         */
+        public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {}
+
+        /**
+         * Invoked when the outgoing {@code Call} has finished dialing but is sending DTMF signals
+         * that were embedded into the outgoing number.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param remainingPostDialSequence The post-dial characters that remain to be sent.
+         */
+        public void onPostDial(Call call, String remainingPostDialSequence) {}
+
+        /**
+         * Invoked when the post-dial sequence in the outgoing {@code Call} has reached a pause
+         * character. This causes the post-dial signals to stop pending user confirmation. An
+         * implementation should present this choice to the user and invoke
+         * {@link #postDialContinue(boolean)} when the user makes the choice.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param remainingPostDialSequence The post-dial characters that remain to be sent.
+         */
+        public void onPostDialWait(Call call, String remainingPostDialSequence) {}
+
+        /**
+         * Invoked when the {@code RemoteCallVideoProvider} of the {@code Call} has changed.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param callVideoProvider The {@code RemoteCallVideoProvider} associated with the
+         * {@code Call}.
+         */
+
+        public void onCallVideoProviderChanged(Call call,
+                RemoteCallVideoProvider callVideoProvider) {}
+
+        /**
+         * Invoked when the {@code Call} is destroyed. Clients should refrain from cleaning
+         * up their UI for the {@code Call} in response to state transitions. Specifically,
+         * clients should not assume that a {@link #onStateChanged(Call, int)} with a state of
+         * {@link #STATE_DISCONNECTED} is the final notification the {@code Call} will send. Rather,
+         * clients should wait for this method to be invoked.
+         *
+         * @param call The {@code Call} being destroyed.
+         */
+        public void onCallDestroyed(Call call) {}
+    }
+
+    private final Phone mPhone;
+    private final String mTelecommCallId;
+    private final InCallAdapter mInCallAdapter;
+    private Call mParent = null;
+    private int mState;
+    private final List<Call> mChildren = new ArrayList<>();
+    private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
+    private List<String> mCannedTextResponses = null;
+    private String mRemainingPostDialSequence;
+    private RemoteCallVideoProvider mCallVideoProvider;
+    private Details mDetails;
+    private final List<Listener> mListeners = new ArrayList<>();
+
+    /**
+     * Obtains the post-dial sequence remaining to be emitted by this {@code Call}, if any.
+     *
+     * @return The remaining post-dial sequence, or {@code null} if there is no post-dial sequence
+     * remaining or this {@code Call} is not in a post-dial state.
+     */
+    public String getRemainingPostDialSequence() {
+        return mRemainingPostDialSequence;
+    }
+
+    /**
+     * Instructs this {@link #STATE_RINGING} {@code Call} to answer.
+     */
+    public void answer() {
+        mInCallAdapter.answerCall(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@link #STATE_RINGING} {@code Call} to reject.
+     *
+     * @param rejectWithMessage Whether to reject with a text message.
+     * @param textMessage An optional text message with which to respond.
+     */
+    public void reject(boolean rejectWithMessage, String textMessage) {
+        mInCallAdapter.rejectCall(mTelecommCallId, rejectWithMessage, textMessage);
+    }
+
+    /**
+     * Instructs this {@code Call} to disconnect.
+     */
+    public void disconnect() {
+        mInCallAdapter.disconnectCall(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to go on hold.
+     */
+    public void hold() {
+        mInCallAdapter.holdCall(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@link #STATE_HOLDING} call to release from hold.
+     */
+    public void unhold() {
+        mInCallAdapter.unholdCall(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to play a dual-tone multi-frequency signaling (DTMF) tone.
+     *
+     * Any other currently playing DTMF tone in the specified call is immediately stopped.
+     *
+     * @param digit A character representing the DTMF digit for which to play the tone. This
+     *         value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
+     */
+    public void playDtmfTone(char digit) {
+        mInCallAdapter.playDtmfTone(mTelecommCallId, digit);
+    }
+
+    /**
+     * Instructs this {@code Call} to stop any dual-tone multi-frequency signaling (DTMF) tone
+     * currently playing.
+     *
+     * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is
+     * currently playing, this method will do nothing.
+     */
+    public void stopDtmfTone() {
+        mInCallAdapter.stopDtmfTone(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to continue playing a post-dial DTMF string.
+     *
+     * A post-dial DTMF string is a string of digits entered after a phone number, when dialed,
+     * that are immediately sent as DTMF tones to the recipient as soon as the connection is made.
+     * While these tones are playing, this {@code Call} will notify listeners via
+     * {@link Listener#onPostDial(Call, String)}.
+     *
+     * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_PAUSE} symbol, this
+     * {@code Call} will temporarily pause playing the tones for a pre-defined period of time.
+     *
+     * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_WAIT} symbol, this
+     * {@code Call} will pause playing the tones and notify listeners via
+     * {@link Listener#onPostDialWait(Call, String)}. At this point, the in-call app
+     * should display to the user an indication of this state and an affordance to continue
+     * the postdial sequence. When the user decides to continue the postdial sequence, the in-call
+     * app should invoke the {@link #postDialContinue(boolean)} method.
+     *
+     * @param proceed Whether or not to continue with the post-dial sequence.
+     */
+    public void postDialContinue(boolean proceed) {
+        mInCallAdapter.postDialContinue(mTelecommCallId, proceed);
+    }
+
+    /**
+     * Notifies this {@code Call} that the phone account user interface element was touched.
+     *
+     * TODO(ihab): Figure out if and how we can generalize this
+     */
+    public void phoneAccountClicked() {
+        mInCallAdapter.phoneAccountClicked(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to enter a conference.
+     */
+    public void conference() {
+        mInCallAdapter.conference(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to split from any conference call with which it may be
+     * connected.
+     */
+    public void splitFromConference() {
+        mInCallAdapter.splitFromConference(mTelecommCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to swap itself with an existing background call, if one
+     * such call exists.
+     */
+    public void swapWithBackgroundCall() {
+        mInCallAdapter.swapWithBackgroundCall(mTelecommCallId);
+    }
+
+    /**
+     * Obtains the parent of this {@code Call} in a conference, if any.
+     *
+     * @return The parent {@code Call}, or {@code null} if this {@code Call} is not a
+     * child of any conference {@code Call}s.
+     */
+    public Call getParent() {
+        return mParent;
+    }
+
+    /**
+     * Obtains the children of this conference {@code Call}, if any.
+     *
+     * @return The children of this {@code Call} if this {@code Call} is a conference, or an empty
+     * {@code List} otherwise.
+     */
+    public List<Call> getChildren() {
+        return mUnmodifiableChildren;
+    }
+
+    /**
+     * Obtains the state of this {@code Call}.
+     *
+     * @return A state value, chosen from the {@code STATE_*} constants.
+     */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Obtains a list of canned, pre-configured message responses to present to the user as
+     * ways of rejecting this {@code Call} using via a text message.
+     *
+     * @see #reject(boolean, String)
+     *
+     * @return A list of canned text message responses.
+     */
+    public List<String> getCannedTextResponses() {
+        return mCannedTextResponses;
+    }
+
+    /**
+     * Obtains an object that can be used to display video from this {@code Call}.
+     *
+     * @return An {@code ICallVideoProvider}.
+     */
+    public RemoteCallVideoProvider getCallVideoProvider() {
+        return mCallVideoProvider;
+    }
+
+    /**
+     * Obtains an object containing call details.
+     *
+     * @return A {@link Details} object. Depending on the state of the {@code Call}, the
+     * result may be {@code null}.
+     */
+    public Details getDetails() {
+        return mDetails;
+    }
+
+    /**
+     * Adds a listener to this {@code Call}.
+     *
+     * @param listener A {@code Listener}.
+     */
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from this {@code Call}.
+     *
+     * @param listener A {@code Listener}.
+     */
+    public void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    /** {@hide} */
+    Call(Phone phone, String telecommCallId, InCallAdapter inCallAdapter) {
+        mPhone = phone;
+        mTelecommCallId = telecommCallId;
+        mInCallAdapter = inCallAdapter;
+        mState = STATE_NEW;
+    }
+
+    /** {@hide} */
+    final String internalGetCallId() {
+        return mTelecommCallId;
+    }
+
+    /** {@hide} */
+    final void internalUpdate(InCallCall inCallCall) {
+        // First, we update the internal state as far as possible before firing any updates.
+
+        Details details = new Details(
+                inCallCall.getHandle(),
+                inCallCall.getHandlePresentation(),
+                inCallCall.getCallerDisplayName(),
+                inCallCall.getCallerDisplayNamePresentation(),
+                inCallCall.getAccount(),
+                inCallCall.getCapabilities(),
+                inCallCall.getDisconnectCauseCode(),
+                inCallCall.getDisconnectCauseMsg(),
+                inCallCall.getConnectTimeMillis(),
+                inCallCall.getGatewayInfo(),
+                inCallCall.getVideoState());
+        boolean detailsChanged = !Objects.equals(mDetails, details);
+        if (detailsChanged) {
+            mDetails = details;
+        }
+
+        boolean cannedTextResponsesChanged = false;
+        if (mCannedTextResponses == null && inCallCall.getCannedSmsResponses() != null
+                && !inCallCall.getCannedSmsResponses().isEmpty()) {
+            mCannedTextResponses = Collections.unmodifiableList(inCallCall.getCannedSmsResponses());
+        }
+
+        boolean callVideoProviderChanged = false;
+        try {
+            callVideoProviderChanged =
+                    !Objects.equals(mCallVideoProvider, inCallCall.getCallVideoProvider());
+            if (callVideoProviderChanged) {
+                mCallVideoProvider = inCallCall.getCallVideoProvider();
+            }
+        } catch (RemoteException e) {
+        }
+
+        int state = stateFromInCallCallState(inCallCall.getState());
+        boolean stateChanged = mState != state;
+        if (stateChanged) {
+            mState = state;
+        }
+
+        if (inCallCall.getParentCallId() != null) {
+            mParent = mPhone.internalGetCallByTelecommId(inCallCall.getParentCallId());
+        }
+
+        mChildren.clear();
+        if (inCallCall.getChildCallIds() != null) {
+            for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) {
+                mChildren.add(mPhone.internalGetCallByTelecommId(
+                        inCallCall.getChildCallIds().get(i)));
+            }
+        }
+
+        // Now we fire updates, ensuring that any client who listens to any of these notifications
+        // gets the most up-to-date state.
+
+        if (stateChanged) {
+            fireStateChanged(mState);
+        }
+        if (detailsChanged) {
+            fireDetailsChanged(mDetails);
+        }
+        if (cannedTextResponsesChanged) {
+            fireCannedTextResponsesLoaded(mCannedTextResponses);
+        }
+        if (callVideoProviderChanged) {
+            fireCallVideoProviderChanged(mCallVideoProvider);
+        }
+
+        // If we have transitioned to DISCONNECTED, that means we need to notify clients and
+        // remove ourselves from the Phone. Note that we do this after completing all state updates
+        // so a client can cleanly transition all their UI to the state appropriate for a
+        // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list.
+        if (mState == STATE_DISCONNECTED) {
+            fireCallDestroyed();
+            mPhone.internalRemoveCall(this);
+        }
+    }
+
+    /** {@hide} */
+    final void internalSetPostDial(String remaining) {
+        mRemainingPostDialSequence = remaining;
+        firePostDial(mRemainingPostDialSequence);
+    }
+
+    /** {@hide} */
+    final void internalSetPostDialWait(String remaining) {
+        mRemainingPostDialSequence = remaining;
+        firePostDialWait(mRemainingPostDialSequence);
+    }
+
+    private void fireStateChanged(int newState) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onStateChanged(this, newState);
+        }
+    }
+
+    private void fireParentChanged(Call newParent) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onParentChanged(this, newParent);
+        }
+    }
+
+    private void fireChildrenChanged(List<Call> children) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onChildrenChanged(this, children);
+        }
+    }
+
+    private void fireDetailsChanged(Details details) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onDetailsChanged(this, details);
+        }
+    }
+
+    private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onCannedTextResponsesLoaded(this, cannedTextResponses);
+        }
+    }
+
+    private void fireCallVideoProviderChanged(RemoteCallVideoProvider callVideoProvider) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onCallVideoProviderChanged(this, callVideoProvider);
+        }
+    }
+
+    private void firePostDial(String remainingPostDialSequence) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onPostDial(this, remainingPostDialSequence);
+        }
+    }
+
+    private void firePostDialWait(String remainingPostDialSequence) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onPostDialWait(this, remainingPostDialSequence);
+        }
+    }
+
+    private void fireCallDestroyed() {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onCallDestroyed(this);
+        }
+    }
+
+    private int stateFromInCallCallState(CallState inCallCallState) {
+        switch (inCallCallState) {
+            case NEW:
+                return STATE_NEW;
+            case DIALING:
+                return STATE_DIALING;
+            case RINGING:
+                return STATE_RINGING;
+            case ACTIVE:
+                return STATE_ACTIVE;
+            case ON_HOLD:
+                return STATE_HOLDING;
+            case DISCONNECTED:
+                return STATE_DISCONNECTED;
+            case ABORTED:
+                return STATE_DISCONNECTED;
+            default:
+                Log.wtf(this, "Unrecognized CallState %s", inCallCallState);
+                return STATE_NEW;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecomm/CallPropertyPresentation.java b/telecomm/java/android/telecomm/CallPropertyPresentation.java
index 350980c..319e565 100644
--- a/telecomm/java/android/telecomm/CallPropertyPresentation.java
+++ b/telecomm/java/android/telecomm/CallPropertyPresentation.java
@@ -19,14 +19,14 @@
 /** Defines how numbers and names are displayed in caller id. */
 public class CallPropertyPresentation {
     /** Property is displayed normally. */
-    public static final int ALLOWED = 0;
+    public static final int ALLOWED = 1;
 
     /** Property was blocked. */
-    public static final int RESTRICTED = 1;
+    public static final int RESTRICTED = 2;
 
     /** Presentation was not specified or is unknown. */
-    public static final int UNKNOWN = 2;
+    public static final int UNKNOWN = 3;
 
     /** Property should be displayed as a pay phone. */
-    public static final int PAYPHONE = 3;
+    public static final int PAYPHONE = 4;
 }
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.java b/telecomm/java/android/telecomm/CallServiceDescriptor.java
deleted file mode 100644
index 5ae07d3..0000000
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.java
+++ /dev/null
@@ -1,233 +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.telecomm;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-
-import java.util.Locale;
-import java.util.UUID;
-
-/**
- * An immutable object containing information about a given {@link ConnectionService}. Instances are
- * created using the enclosed {@link Builder}.
- */
-public final class CallServiceDescriptor implements Parcelable {
-    private static final String TAG = CallServiceDescriptor.class.getSimpleName();
-
-    /**
-     * A placeholder value indicating an invalid network type.
-     * @hide
-     */
-    private static final int FLAG_INVALID = 0;
-
-    /**
-     * Indicates that the device must be connected to a Wi-Fi network in order for the backing
-     * {@link ConnectionService} to be used.
-     */
-    public static final int FLAG_WIFI = 0x01;
-
-    /**
-     * Indicates that the device must be connected to a cellular PSTN network in order for the
-     * backing {@link ConnectionService} to be used.
-     */
-    public static final int FLAG_PSTN = 0x02;
-
-    /**
-     * Indicates that the device must be connected to a cellular data network in order for the
-     * backing {@link ConnectionService} to be used.
-     */
-    public static final int FLAG_MOBILE = 0x04;
-
-    /**
-     * Represents all of the defined FLAG_ constants so validity can be easily checked.
-     * @hide
-     */
-    public static final int FLAG_ALL = FLAG_WIFI | FLAG_PSTN | FLAG_MOBILE;
-
-    /**
-     * A unique ID used to identify a given instance.
-     */
-    private final String mConnectionServiceId;
-
-    /**
-     * The {@link ComponentName} of the {@link ConnectionService} implementation which this is
-     * describing.
-     */
-    private final ComponentName mComponentName;
-
-    /**
-     * The type of connection that the {@link ConnectionService} requires; will be one of the FLAG_*
-     * constants defined in this class.
-     */
-    private final int mNetworkType;
-
-    private CallServiceDescriptor(
-            String connectionServiceId,
-            ComponentName componentName,
-            int networkType) {
-
-        mConnectionServiceId = connectionServiceId;
-        mComponentName = componentName;
-        mNetworkType = networkType;
-    }
-
-    /**
-     * @return The ID used to identify this {@link ConnectionService}.
-     */
-    public String getConnectionServiceId() {
-        return mConnectionServiceId;
-    }
-
-    /**
-     * @return The {@link ComponentName} of the {@link ConnectionService}.
-     */
-    public ComponentName getServiceComponent() {
-        return mComponentName;
-    }
-
-    /**
-     * @return The network type required by the {@link ConnectionService} to place a call.
-     */
-    public int getNetworkType() {
-        return mNetworkType;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (!(obj instanceof CallServiceDescriptor)) {
-            return false;
-        }
-        CallServiceDescriptor descriptor = (CallServiceDescriptor) obj;
-        return mConnectionServiceId.equals(descriptor.mConnectionServiceId) &&
-                mComponentName.equals(descriptor.mComponentName) &&
-                mNetworkType == descriptor.mNetworkType;
-    }
-
-    @Override
-    public String toString() {
-        return String.format(Locale.US, "[%s, component: %s]",
-                CallServiceDescriptor.class.getSimpleName(),
-                mComponentName == null ? "null" : mComponentName.flattenToShortString());
-    }
-
-    /**
-     * @param context {@link Context} to use for the construction of the {@link Builder}.
-     * @return A new {@link Builder} instance.
-     */
-    public static Builder newBuilder(Context context) {
-        return new Builder(context);
-    }
-
-    /**
-     * Creates {@link CallServiceDescriptor} instances. Builders should be created with the
-     * {@link CallServiceDescriptor#newBuilder(Context)} method.
-     */
-    public static class Builder {
-        /** The {@link Context} to use to verify {@link ComponentName} ownership. */
-        private Context mContext;
-
-        /** The {@link ComponentName} pointing to the backing {@link ConnectionService}. */
-        private ComponentName mComponentName;
-
-        /** The required network type that the {@link ConnectionService} needs. */
-        private int mNetworkType = FLAG_INVALID;
-
-        private Builder(Context context) {
-            mContext = context;
-        }
-
-        /**
-         * Set which {@link ConnectionService} this {@link CallServiceDescriptor} is describing.
-         *
-         * @param serviceClass The {@link ConnectionService} class
-         * @return This {@link Builder} for method chaining.
-         */
-        public Builder setConnectionService(Class<? extends ConnectionService> serviceClass) {
-            mComponentName = new ComponentName(mContext, serviceClass);
-            return this;
-        }
-
-        /**
-         * Which network type the backing {@link ConnectionService} requires. This must be one of
-         * the {@link CallServiceDescriptor}.TYPE_* fields.
-         *
-         * @param networkType Which network type the backing {@link ConnectionService} requires.
-         * @return This {@link Builder} for method chaining.
-         */
-        public Builder setNetworkType(int networkType) {
-            mNetworkType = networkType;
-            return this;
-        }
-
-        /**
-         * @return A constructed {@link CallServiceDescriptor} object.
-         */
-        public CallServiceDescriptor build() {
-            // STOPSHIP: Verify validity of ComponentName (permissions, intents, etc)
-
-            // Make sure that they passed in a valid network flag combination
-            if (mNetworkType == FLAG_INVALID || ((mNetworkType & FLAG_ALL) == 0)) {
-
-                Log.wtf(TAG, "Invalid network type for " + mComponentName);
-                // Revert them back to TYPE_INVALID so it won't be considered.
-                mNetworkType = FLAG_INVALID;
-            }
-
-            // TODO: Should we use a sha1 of the ComponentName? Would prevent duplicates.
-            return new CallServiceDescriptor(
-                UUID.randomUUID().toString(), mComponentName, mNetworkType);
-        }
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mConnectionServiceId);
-        dest.writeParcelable(mComponentName, 0);
-        dest.writeInt(mNetworkType);
-    }
-
-    public static final Creator<CallServiceDescriptor> CREATOR =
-            new Creator<CallServiceDescriptor>() {
-        @Override
-        public CallServiceDescriptor createFromParcel(Parcel source) {
-            String id = source.readString();
-            ComponentName componentName = source.readParcelable(
-                    CallServiceDescriptor.class.getClassLoader());
-            int networkType = source.readInt();
-
-            return new CallServiceDescriptor(id, componentName, networkType);
-        }
-
-        @Override
-        public CallServiceDescriptor[] newArray(int size) {
-            return new CallServiceDescriptor[size];
-        }
-    };
-}
diff --git a/telecomm/java/android/telecomm/CallServiceLookupResponse.java b/telecomm/java/android/telecomm/CallServiceLookupResponse.java
deleted file mode 100644
index dd35a24..0000000
--- a/telecomm/java/android/telecomm/CallServiceLookupResponse.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telecomm;
-
-import android.os.RemoteException;
-
-import com.android.internal.telecomm.ICallServiceLookupResponse;
-
-import java.util.List;
-
-/**
- * Used by {@link CallServiceProvider} to return a list of {@link CallServiceDescriptor}s.
- */
-public final class CallServiceLookupResponse {
-    private final ICallServiceLookupResponse mResponse;
-
-    /**
-     * {@hide}
-     */
-    public CallServiceLookupResponse(ICallServiceLookupResponse response) {
-        mResponse = response;
-    }
-
-    /**
-     * Passes the sorted list of preferred {@link CallServiceDescriptor}s back to Telecomm.  Used
-     * in the context of attempting to place a pending outgoing call.
-     *
-     * @param callServiceDescriptors The set of call-service descriptors from
-     * {@link CallServiceProvider}.
-     */
-    public void setCallServiceDescriptors(List<CallServiceDescriptor> callServiceDescriptors) {
-        try {
-            mResponse.setCallServiceDescriptors(callServiceDescriptors);
-        } catch (RemoteException e) {
-        }
-    }
-}
diff --git a/telecomm/java/android/telecomm/CallServiceProvider.java b/telecomm/java/android/telecomm/CallServiceProvider.java
deleted file mode 100644
index c50334a..0000000
--- a/telecomm/java/android/telecomm/CallServiceProvider.java
+++ /dev/null
@@ -1,109 +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.telecomm;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-
-import com.android.internal.telecomm.ICallServiceLookupResponse;
-import com.android.internal.telecomm.ICallServiceProvider;
-
-/**
- * Base implementation of a call service provider which extends {@link Service}. This class
- * should be extended by an app that wants to supply phone calls to be handled and managed by
- * the device's in-call interface. All method-calls from the framework to the call service provider
- * are passed through to the main thread for before executing the overriden methods of
- * CallServiceProvider.
- *
- * TODO(santoscordon): Improve paragraph above once the final design is in place. Needs more
- * about how this can be used.
- */
-public abstract class CallServiceProvider extends Service {
-
-    /**
-     * Default Handler used to consolidate binder method calls onto a single thread.
-     */
-    private final class CallServiceProviderMessageHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_LOOKUP_CALL_SERVICES:
-                    CallServiceLookupResponse response =
-                            new CallServiceLookupResponse((ICallServiceLookupResponse) msg.obj);
-                    lookupCallServices(response);
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Default ICallServiceProvider implementation provided to CallsManager via {@link #onBind}.
-     */
-    private final class CallServiceProviderWrapper extends ICallServiceProvider.Stub {
-        /** {@inheritDoc} */
-        @Override
-        public void lookupCallServices(ICallServiceLookupResponse callServiceLookupResponse) {
-            Message message = mMessageHandler.obtainMessage(
-                    MSG_LOOKUP_CALL_SERVICES, callServiceLookupResponse);
-            message.sendToTarget();
-        }
-    }
-
-    // Only used internally by this class.
-    // Binder method calls on this service can occur on multiple threads. These messages are used
-    // in conjunction with {@link #mMessageHandler} to ensure that all callbacks are handled on a
-    // single thread.  Keeping it on a single thread allows CallService implementations to avoid
-    // needing multi-threaded code in their own callback routines.
-    private static final int MSG_LOOKUP_CALL_SERVICES = 1;
-
-    /**
-     * Message handler for consolidating binder callbacks onto a single thread.
-     * See {@link CallServiceProviderMessageHandler}.
-     */
-    private final CallServiceProviderMessageHandler mMessageHandler;
-
-    /**
-     * Default binder implementation of {@link ICallServiceProvider} interface.
-     */
-    private final CallServiceProviderWrapper mBinder;
-
-    /**
-     * Protected constructor called only by subclasses creates the binder interface and
-     * single-threaded message handler.
-     */
-    protected CallServiceProvider() {
-        mMessageHandler = new CallServiceProviderMessageHandler();
-        mBinder = new CallServiceProviderWrapper();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mBinder;
-    }
-
-    /**
-     * Initiates the process to retrieve the list of {@link CallServiceDescriptor}s implemented by
-     * this provider.
-     *
-     * @param response The response object through which the list of call services is sent.
-     */
-    public abstract void lookupCallServices(CallServiceLookupResponse response);
-}
diff --git a/telecomm/java/android/telecomm/CallState.java b/telecomm/java/android/telecomm/CallState.java
index 152c2023..a464da5 100644
--- a/telecomm/java/android/telecomm/CallState.java
+++ b/telecomm/java/android/telecomm/CallState.java
@@ -48,22 +48,6 @@
     RINGING,
 
     /**
-     * Indicates that the call is active but in a "post-dial" state where Telecomm is now sending
-     * some dual-tone multi-frequency signaling (DTMF) tones appended to the dialed number. Normal
-     * transitions are to {@link #POST_DIAL_WAIT} when the post-dial string requires user
-     * confirmation to proceed, {@link #ACTIVE} when the post-dial tones are completed, or
-     * {@link #DISCONNECTED}.
-     */
-    POST_DIAL,
-
-    /**
-     * Indicates that the call was in the {@link #POST_DIAL} state but is now waiting for user
-     * confirmation before the remaining digits can be sent. Normal transitions are to
-     * {@link #POST_DIAL} when the user asks Telecomm to proceed with the post-dial sequence.
-     */
-    POST_DIAL_WAIT,
-
-    /**
      * Indicates that a call is currently connected to another party and a communication channel is
      * open between them. The normal transition to this state is by the user answering a
      * {@link #DIALING} call or a {@link #RINGING} call being answered by the other party.
diff --git a/telecomm/java/android/telecomm/CallVideoClient.java b/telecomm/java/android/telecomm/CallVideoClient.java
index 76b28fa..fb970dc 100644
--- a/telecomm/java/android/telecomm/CallVideoClient.java
+++ b/telecomm/java/android/telecomm/CallVideoClient.java
@@ -241,6 +241,7 @@
      *
      * @param callCameraCapabilities The changed camera capabilities.
      */
-    public abstract void onHandleCameraCapabilitiesChange(CallCameraCapabilities callCameraCapabilities);
+    public abstract void onHandleCameraCapabilitiesChange(
+            CallCameraCapabilities callCameraCapabilities);
 }
 
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index 0db9e29..5888d6a 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -30,11 +30,11 @@
     // TODO: Token to limit recursive invocations
     // TODO: Consider upgrading "mHandle" to ordered list of handles, indicating a set of phone
     //         numbers that would satisfy the client's needs, in order of preference
+    private final PhoneAccount mAccount;
     private final String mCallId;
     private final Uri mHandle;
     private final int mHandlePresentation;
     private final Bundle mExtras;
-    private final PhoneAccount mAccount;
     private final int mVideoState;
 
     /**
@@ -61,6 +61,15 @@
         mVideoState = videoState;
     }
 
+    private ConnectionRequest(Parcel in) {
+        mAccount = in.readParcelable(getClass().getClassLoader());
+        mCallId = in.readString();
+        mHandle = in.readParcelable(getClass().getClassLoader());
+        mHandlePresentation = in.readInt();
+        mExtras = in.readParcelable(getClass().getClassLoader());
+        mVideoState = in.readInt();
+    }
+
     /**
      * The account which should be used to place the call.
      */
@@ -109,26 +118,17 @@
                 mExtras == null ? "" : mExtras);
     }
 
-    public static final Parcelable.Creator<ConnectionRequest> CREATOR =
-            new Parcelable.Creator<ConnectionRequest> () {
-                @Override
-                public ConnectionRequest createFromParcel(Parcel source) {
-                    PhoneAccount account = (PhoneAccount) source.readParcelable(
-                            getClass().getClassLoader());
-                    String callId = source.readString();
-                    Uri handle = (Uri) source.readParcelable(getClass().getClassLoader());
-                    int presentation = source.readInt();
-                    Bundle extras = (Bundle) source.readParcelable(getClass().getClassLoader());
-                    int videoState = source.readInt();
-                    return new ConnectionRequest(
-                            account, callId, handle, presentation, extras, videoState);
-                }
+    public static final Creator<ConnectionRequest> CREATOR = new Creator<ConnectionRequest> () {
+        @Override
+        public ConnectionRequest createFromParcel(Parcel source) {
+            return new ConnectionRequest(source);
+        }
 
-                @Override
-                public ConnectionRequest[] newArray(int size) {
-                    return new ConnectionRequest[size];
-                }
-            };
+        @Override
+        public ConnectionRequest[] newArray(int size) {
+            return new ConnectionRequest[size];
+        }
+    };
 
     /**
      * {@inheritDoc}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 1966081..178cee8 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -47,22 +47,21 @@
     private static final Connection NULL_CONNECTION = new Connection() {};
 
     private static final int MSG_ADD_CALL_SERVICE_ADAPTER = 1;
-    private static final int MSG_CALL = 2;
+    private static final int MSG_CREATE_CONNECTION = 2;
     private static final int MSG_ABORT = 3;
-    private static final int MSG_CREATE_INCOMING_CALL = 4;
-    private static final int MSG_ANSWER = 5;
-    private static final int MSG_REJECT = 6;
-    private static final int MSG_DISCONNECT = 7;
-    private static final int MSG_HOLD = 8;
-    private static final int MSG_UNHOLD = 9;
-    private static final int MSG_ON_AUDIO_STATE_CHANGED = 10;
-    private static final int MSG_PLAY_DTMF_TONE = 11;
-    private static final int MSG_STOP_DTMF_TONE = 12;
-    private static final int MSG_CONFERENCE = 13;
-    private static final int MSG_SPLIT_FROM_CONFERENCE = 14;
-    private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 15;
-    private static final int MSG_ON_POST_DIAL_CONTINUE = 16;
-    private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 17;
+    private static final int MSG_ANSWER = 4;
+    private static final int MSG_REJECT = 5;
+    private static final int MSG_DISCONNECT = 6;
+    private static final int MSG_HOLD = 7;
+    private static final int MSG_UNHOLD = 8;
+    private static final int MSG_ON_AUDIO_STATE_CHANGED = 9;
+    private static final int MSG_PLAY_DTMF_TONE = 10;
+    private static final int MSG_STOP_DTMF_TONE = 11;
+    private static final int MSG_CONFERENCE = 12;
+    private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
+    private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 14;
+    private static final int MSG_ON_POST_DIAL_CONTINUE = 15;
+    private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 16;
 
     private final Map<String, Connection> mConnectionById = new HashMap<>();
     private final Map<Connection, String> mIdByConnection = new HashMap<>();
@@ -74,11 +73,11 @@
     private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
 
     /**
-     * A callback for providing the resuilt of creating a connection.
+     * A callback for providing the result of creating a connection.
      */
-    public interface OutgoingCallResponse<CONNECTION> {
+    public interface CreateConnectionResponse<CONNECTION> {
         /**
-         * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
+         * Tells Telecomm that an attempt to create the connection succeeded.
          *
          * @param request The original request.
          * @param connection The connection.
@@ -86,7 +85,8 @@
         void onSuccess(ConnectionRequest request, CONNECTION connection);
 
         /**
-         * Tells Telecomm that an attempt to place the specified outgoing call failed.
+         * Tells Telecomm that an attempt to create the connection failed. Telecomm will try a
+         * different service until a service cancels the process or completes it successfully.
          *
          * @param request The original request.
          * @param code An integer code indicating the reason for failure.
@@ -95,7 +95,8 @@
         void onFailure(ConnectionRequest request, int code, String msg);
 
         /**
-         * Tells Telecomm to cancel the call.
+         * Tells Telecomm to cancel creating the connection. Telecomm will stop trying to create
+         * the connection an no more services will be tried.
          *
          * @param request The original request.
          */
@@ -109,8 +110,9 @@
         }
 
         @Override
-        public void call(ConnectionRequest request) {
-            mHandler.obtainMessage(MSG_CALL, request).sendToTarget();
+        public void createConnection(ConnectionRequest request, boolean isIncoming) {
+            mHandler.obtainMessage(
+                    MSG_CREATE_CONNECTION, isIncoming ? 1 : 0, 0, request).sendToTarget();
         }
 
         @Override
@@ -119,11 +121,6 @@
         }
 
         @Override
-        public void createIncomingCall(ConnectionRequest request) {
-            mHandler.obtainMessage(MSG_CREATE_INCOMING_CALL, request).sendToTarget();
-        }
-
-        @Override
         public void answer(String callId) {
             mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
         }
@@ -206,15 +203,12 @@
                     mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
                     onAdapterAttached();
                     break;
-                case MSG_CALL:
-                    call((ConnectionRequest) msg.obj);
+                case MSG_CREATE_CONNECTION:
+                    createConnection((ConnectionRequest) msg.obj, msg.arg1 == 1);
                     break;
                 case MSG_ABORT:
                     abort((String) msg.obj);
                     break;
-                case MSG_CREATE_INCOMING_CALL:
-                    createIncomingCall((ConnectionRequest) msg.obj);
-                    break;
                 case MSG_ANSWER:
                     answer((String) msg.obj);
                     break;
@@ -394,29 +388,39 @@
         return mBinder;
     }
 
-    private void call(final ConnectionRequest originalRequest) {
+    /**
+     * This can be used by telecomm to either create a new outgoing call or attach to an existing
+     * incoming call. In either case, telecomm will cycle through a set of services and call
+     * createConnection util a connection service cancels the process or completes it successfully.
+     */
+    private void createConnection(ConnectionRequest originalRequest, boolean isIncoming) {
         Log.d(this, "call %s", originalRequest);
-        onCreateConnections(
-                originalRequest,
-                new OutgoingCallResponse<Connection>() {
-                    @Override
-                    public void onSuccess(ConnectionRequest request, Connection connection) {
-                        Log.d(this, "adapter handleSuccessfulOutgoingCall %s", request.getCallId());
-                        mAdapter.handleSuccessfulOutgoingCall(request);
-                        addConnection(request.getCallId(), connection);
-                    }
+        CreateConnectionResponse response = new CreateConnectionResponse<Connection>() {
+            @Override
+            public void onSuccess(ConnectionRequest request, Connection connection) {
+                Log.d(this, "adapter handleCreateConnectionSuccessful %s",
+                        request.getCallId());
+                mAdapter.handleCreateConnectionSuccessful(request);
+                addConnection(request.getCallId(), connection);
+            }
 
-                    @Override
-                    public void onFailure(ConnectionRequest request, int code, String msg) {
-                        mAdapter.handleFailedOutgoingCall(request, code, msg);
-                    }
+            @Override
+            public void onFailure(ConnectionRequest request, int code, String msg) {
+                // Tell telecomm to try a different service.
+                mAdapter.handleCreateConnectionFailed(request, code, msg);
+            }
 
-                    @Override
-                    public void onCancel(ConnectionRequest request) {
-                        mAdapter.cancelOutgoingCall(request);
-                    }
-                }
-        );
+            @Override
+            public void onCancel(ConnectionRequest request) {
+                // Tell telecomm not to attempt any more services.
+                mAdapter.handleCreateConnectionCancelled(request);
+            }
+        };
+        if (isIncoming) {
+            onCreateIncomingConnection(originalRequest, response);
+        } else {
+            onCreateOutgoingConnection(originalRequest, response);
+        }
     }
 
     private void abort(String callId) {
@@ -424,33 +428,6 @@
         findConnectionForAction(callId, "abort").onAbort();
     }
 
-    private void createIncomingCall(ConnectionRequest originalRequest) {
-        Log.d(this, "createIncomingCall %s", originalRequest);
-        onCreateIncomingConnection(
-                originalRequest,
-                new Response<ConnectionRequest, Connection>() {
-                    @Override
-                    public void onResult(ConnectionRequest request, Connection... result) {
-                        if (result != null && result.length != 1) {
-                            for (Connection c : result) {
-                                c.onAbort();
-                            }
-                        } else {
-                            addConnection(request.getCallId(), result[0]);
-                            Log.d(this, "adapter notifyIncomingCall %s", request);
-                            mAdapter.notifyIncomingCall(request);
-                        }
-                    }
-
-                    @Override
-                    public void onError(ConnectionRequest request, int code, String msg) {
-                        Log.d(this, "adapter failed createIncomingCall %s %d %s",
-                                request, code, msg);
-                    }
-                }
-        );
-    }
-
     private void answer(String callId) {
         Log.d(this, "answer %s", callId);
         findConnectionForAction(callId, "answer").onAnswer();
@@ -570,7 +547,7 @@
                                     IConnectionService.Stub.asInterface(services.get(i)));
                         }
                         mAreAccountsInitialized = true;
-                        Log.d(this, "remote call services found: " + services);
+                        Log.d(this, "remote connection services found: " + services);
                         maybeRespondToAccountLookup();
                     }
                 });
@@ -606,10 +583,16 @@
         }
     }
 
+    public final void createRemoteIncomingConnection(
+            ConnectionRequest request,
+            CreateConnectionResponse<RemoteConnection> response) {
+        mRemoteConnectionManager.createRemoteConnection(request, response, true);
+    }
+
     public final void createRemoteOutgoingConnection(
             ConnectionRequest request,
-            OutgoingCallResponse<RemoteConnection> response) {
-        mRemoteConnectionManager.createOutgoingConnection(request, response);
+            CreateConnectionResponse<RemoteConnection> response) {
+        mRemoteConnectionManager.createRemoteConnection(request, response, false);
     }
 
     /**
@@ -620,14 +603,25 @@
     }
 
     /**
-     * Create a Connection given a request.
+     * Create a Connection given an incoming request. This is used to attach to existing incoming
+     * calls.
      *
-     * @param request Data encapsulating details of the desired Connection.
+     * @param request Details about the incoming call.
      * @param callback A callback for providing the result.
      */
-    protected void onCreateConnections(
+    protected void onCreateIncomingConnection(
             ConnectionRequest request,
-            OutgoingCallResponse<Connection> callback) {}
+            CreateConnectionResponse<Connection> callback) {}
+
+    /**
+     * Create a Connection given an outgoing request. This is used to initiate new outgoing calls.
+     *
+     * @param request Details about the outgoing call.
+     * @param callback A callback for providing the result.
+     */
+    protected void onCreateOutgoingConnection(
+            ConnectionRequest request,
+            CreateConnectionResponse<Connection> callback) {}
 
     /**
      * Returns a new or existing conference connection when the the user elects to convert the
@@ -645,21 +639,6 @@
             Response<String, Connection> callback) {}
 
     /**
-     * Create a Connection to match an incoming connection notification.
-     *
-     * IMPORTANT: If the incoming connection has a phone number (or other handle) that the user
-     * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
-     * ConnectionService implementation MUST NOT reveal this phone number as part of the Intent
-     * it sends to notify Telecomm of an incoming connection.
-     *
-     * @param request Data encapsulating details of the desired Connection.
-     * @param callback A callback for providing the result.
-     */
-    protected void onCreateIncomingConnection(
-            ConnectionRequest request,
-            Response<ConnectionRequest, Connection> callback) {}
-
-    /**
      * Notifies that a connection has been added to this connection service and sent to Telecomm.
      *
      * @param connection The connection which was added.
diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
index b90dec3..a812fa4 100644
--- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
@@ -73,65 +73,28 @@
         }
     }
 
-    /**
-     * Provides Telecomm with the details of an incoming call. An invocation of this method must
-     * follow {@link ConnectionService#setIncomingCallId} and use the call ID specified therein.
-     * Upon the invocation of this method, Telecomm will bring up the incoming-call interface where
-     * the user can elect to answer or reject a call.
-     *
-     * @param request The connection request.
-     */
-    void notifyIncomingCall(ConnectionRequest request) {
+    void handleCreateConnectionSuccessful(ConnectionRequest request) {
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.notifyIncomingCall(request);
+                adapter.handleCreateConnectionSuccessful(request);
             } catch (RemoteException e) {
             }
         }
     }
 
-    /**
-     * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
-     *
-     * @param request The originating request for a connection.
-     */
-    void handleSuccessfulOutgoingCall(ConnectionRequest request) {
+    void handleCreateConnectionFailed(ConnectionRequest request, int errorCode, String errorMsg) {
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.handleSuccessfulOutgoingCall(request);
+                adapter.handleCreateConnectionFailed(request, errorCode, errorMsg);
             } catch (RemoteException e) {
             }
         }
     }
 
-    /**
-     * Tells Telecomm that an attempt to place the specified outgoing call failed.
-     *
-     * @param request The originating request for a connection.
-     * @param errorCode The error code associated with the failed call attempt.
-     * @param errorMsg The error message associated with the failed call attempt.
-     */
-    void handleFailedOutgoingCall(
-            ConnectionRequest request,
-            int errorCode,
-            String errorMsg) {
+    void handleCreateConnectionCancelled(ConnectionRequest request) {
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
-            } catch (RemoteException e) {
-            }
-        }
-    }
-
-    /**
-     * Tells Telecomm to cancel the call.
-     *
-     * @param request The originating request for a connection.
-     */
-    void cancelOutgoingCall(ConnectionRequest request) {
-        for (IConnectionServiceAdapter adapter : mAdapters) {
-            try {
-                adapter.cancelOutgoingCall(request);
+                adapter.handleCreateConnectionCancelled(request);
             } catch (RemoteException e) {
             }
         }
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index d8293a5..66cf1df 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -24,7 +24,7 @@
  * Receives commands from {@link InCallService} implementations which should be executed by
  * Telecomm. When Telecomm binds to a {@link InCallService}, an instance of this class is given to
  * the in-call service through which it can manipulate live (active, dialing, ringing) calls. When
- * the in-call service is notified of new calls ({@link InCallService#addCall}), it can use the
+ * the in-call service is notified of new calls, it can use the
  * given call IDs to execute commands such as {@link #answerCall} for incoming calls or
  * {@link #disconnectCall} for active calls the user would like to end. Some commands are only
  * appropriate for calls in certain states; please consult each method for such limitations.
@@ -167,16 +167,15 @@
      * A post-dial DTMF string is a string of digits entered after a phone number, when dialed,
      * that are immediately sent as DTMF tones to the recipient as soon as the connection is made.
      * While these tones are playing, Telecomm will notify the {@link InCallService} that the call
-     * is in the {@link InCallService#setPostDial(String,String)} state.
+     * is in the post dial state.
      *
      * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_PAUSE} symbol, Telecomm
      * will temporarily pause playing the tones for a pre-defined period of time.
      *
      * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_WAIT} symbol, Telecomm
      * will pause playing the tones and notify the {@link InCallService} that the call is in the
-     * {@link InCallService#setPostDialWait(String,String)} state. When the user decides to continue
-     * the postdial sequence, the {@link InCallService} should invoke the
-     * {@link #postDialContinue(String,boolean)} method.
+     * post dial wait state. When the user decides to continue the postdial sequence, the
+     * {@link InCallService} should invoke the {@link #postDialContinue(String,boolean)} method.
      *
      * @param callId The unique ID of the call for which postdial string playing should continue.
      * @param proceed Whether or not to continue with the post-dial sequence.
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 7c35020..db8395c 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -45,12 +45,12 @@
     private final int mCallerDisplayNamePresentation;
     private final GatewayInfo mGatewayInfo;
     private final PhoneAccount mAccount;
-    private final CallServiceDescriptor mCurrentCallServiceDescriptor;
     private final ICallVideoProvider mCallVideoProvider;
     private RemoteCallVideoProvider mRemoteCallVideoProvider;
     private final String mParentCallId;
     private final List<String> mChildCallIds;
     private final StatusHints mStatusHints;
+    private final int mVideoState;
 
     /** @hide */
     public InCallCall(
@@ -67,11 +67,11 @@
             int callerDisplayNamePresentation,
             GatewayInfo gatewayInfo,
             PhoneAccount account,
-            CallServiceDescriptor descriptor,
             ICallVideoProvider callVideoProvider,
             String parentCallId,
             List<String> childCallIds,
-            StatusHints statusHints) {
+            StatusHints statusHints,
+            int videoState) {
         mId = id;
         mState = state;
         mDisconnectCauseCode = disconnectCauseCode;
@@ -85,11 +85,11 @@
         mCallerDisplayNamePresentation = callerDisplayNamePresentation;
         mGatewayInfo = gatewayInfo;
         mAccount = account;
-        mCurrentCallServiceDescriptor = descriptor;
         mCallVideoProvider = callVideoProvider;
         mParentCallId = parentCallId;
         mChildCallIds = childCallIds;
         mStatusHints = statusHints;
+        mVideoState = videoState;
     }
 
     /** The unique ID of the call. */
@@ -165,11 +165,6 @@
         return mAccount;
     }
 
-    /** The descriptor for the call service currently routing this call. */
-    public CallServiceDescriptor getCurrentCallServiceDescriptor() {
-        return mCurrentCallServiceDescriptor;
-    }
-
     /**
      * Returns an object for remotely communicating through the call video provider's binder.
      * @return The call video provider.
@@ -212,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> () {
@@ -232,18 +235,18 @@
             int callerDisplayNamePresentation = source.readInt();
             GatewayInfo gatewayInfo = source.readParcelable(classLoader);
             PhoneAccount account = source.readParcelable(classLoader);
-            CallServiceDescriptor descriptor = source.readParcelable(classLoader);
             ICallVideoProvider callVideoProvider =
                     ICallVideoProvider.Stub.asInterface(source.readStrongBinder());
             String parentCallId = source.readString();
             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, descriptor, callVideoProvider, parentCallId, childCallIds,
-                    statusHints);
+                    account, callVideoProvider, parentCallId, childCallIds, statusHints,
+                    videoState);
         }
 
         @Override
@@ -274,12 +277,12 @@
         destination.writeInt(mCallerDisplayNamePresentation);
         destination.writeParcelable(mGatewayInfo, 0);
         destination.writeParcelable(mAccount, 0);
-        destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
         destination.writeStrongBinder(
                 mCallVideoProvider != null ? mCallVideoProvider.asBinder() : null);
         destination.writeString(mParentCallId);
         destination.writeList(mChildCallIds);
         destination.writeParcelable(mStatusHints, 0);
+        destination.writeInt(mVideoState);
     }
 
     @Override
diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java
index 31291fb..028b6e4 100644
--- a/telecomm/java/android/telecomm/InCallService.java
+++ b/telecomm/java/android/telecomm/InCallService.java
@@ -16,8 +16,6 @@
 
 package android.telecomm;
 
-import android.app.Service;
-import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -31,11 +29,10 @@
  * This service is implemented by any app that wishes to provide the user-interface for managing
  * phone calls. Telecomm binds to this service while there exists a live (active or incoming)
  * call, and uses it to notify the in-call app of any live and and recently disconnected calls.
- * TODO(santoscordon): Needs more/better description of lifecycle once the interface is better
- * defined.
+ *
  * TODO(santoscordon): What happens if two or more apps on a given device implement this interface?
  */
-public abstract class InCallService extends Service {
+public abstract class InCallService {
     private static final int MSG_SET_IN_CALL_ADAPTER = 1;
     private static final int MSG_ADD_CALL = 2;
     private static final int MSG_UPDATE_CALL = 3;
@@ -50,21 +47,21 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SET_IN_CALL_ADAPTER:
-                    mAdapter = new InCallAdapter((IInCallAdapter) msg.obj);
-                    onAdapterAttached(mAdapter);
+                    mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj));
+                    onPhoneCreated(mPhone);
                     break;
                 case MSG_ADD_CALL:
-                    addCall((InCallCall) msg.obj);
+                    mPhone.internalAddCall((InCallCall) msg.obj);
                     break;
                 case MSG_UPDATE_CALL:
-                    updateCall((InCallCall) msg.obj);
+                    mPhone.internalUpdateCall((InCallCall) msg.obj);
                     break;
-                case MSG_SET_POST_DIAL: {
+                                case MSG_SET_POST_DIAL: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
                         String callId = (String) args.arg1;
                         String remaining = (String) args.arg2;
-                        setPostDial(callId, remaining);
+                        mPhone.internalSetPostDial(callId, remaining);
                     } finally {
                         args.recycle();
                     }
@@ -75,17 +72,17 @@
                     try {
                         String callId = (String) args.arg1;
                         String remaining = (String) args.arg2;
-                        setPostDialWait(callId, remaining);
+                        mPhone.internalSetPostDialWait(callId, remaining);
                     } finally {
                         args.recycle();
                     }
                     break;
                 }
                 case MSG_ON_AUDIO_STATE_CHANGED:
-                    onAudioStateChanged((CallAudioState) msg.obj);
+                    mPhone.internalAudioStateChanged((CallAudioState) msg.obj);
                     break;
                 case MSG_BRING_TO_FOREGROUND:
-                    bringToForeground(msg.arg1 == 1);
+                    mPhone.internalBringToForeground(msg.arg1 == 1);
                     break;
                 default:
                     break;
@@ -142,85 +139,41 @@
         }
     }
 
-    private final InCallServiceBinder mBinder;
+    private Phone mPhone;
 
-    private InCallAdapter mAdapter;
+    protected InCallService() {}
 
-    protected InCallService() {
-        mBinder = new InCallServiceBinder();
-    }
-
-    @Override
-    public final IBinder onBind(Intent intent) {
-        return mBinder;
+    public final IBinder getBinder() {
+        return new InCallServiceBinder();
     }
 
     /**
-     * @return The attached {@link InCallAdapter} if attached, or null otherwise.
+     * Obtain the {@code Phone} associated with this {@code InCallService}.
+     *
+     * @return The {@code Phone} object associated with this {@code InCallService}, or {@code null}
+     * if the {@code InCallService} is not in a state where it has an associated {@code Phone}.
      */
-    protected final InCallAdapter getAdapter() {
-        return mAdapter;
+    public Phone getPhone() {
+        return mPhone;
     }
 
     /**
-     * Lifecycle callback which is called when this {@link InCallService} has been attached
-     * to a {@link InCallAdapter}, indicating {@link #getAdapter()} is now safe to use.
+     * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
+     * to start displaying in-call information to the user. Each instance of {@code InCallService}
+     * will have only one {@code Phone}, and this method will be called exactly once in the
+     * lifetime of the {@code InCallService}.
      *
-     * @param adapter The adapter now attached to this in-call service.
+     * @param phone The {@code Phone} object associated with this {@code InCallService}.
      */
-    protected void onAdapterAttached(InCallAdapter adapter) {
-    }
+    public void onPhoneCreated(Phone phone) { }
 
     /**
-     * Indicates to the in-call app that a new call has been created and an appropriate
-     * user-interface should be built and shown to notify the user.
+     * Invoked when a {@code Phone} has been destroyed. This is a signal to the in-call experience
+     * to stop displaying in-call information to the user. This method will be called exactly once
+     * in the lifetime of the {@code InCallService}, and it will always be called after a previous
+     * call to {@link #onPhoneCreated(Phone)}.
      *
-     * @param call Information about the new call.
+     * @param phone The {@code Phone} object associated with this {@code InCallService}.
      */
-     protected abstract void addCall(InCallCall call);
-
-    /**
-     * Call when information about a call has changed.
-     *
-     * @param call Information about the new call.
-     */
-     protected abstract void updateCall(InCallCall call);
-
-    /**
-     * Indicates to the in-call app that the specified call is active but in a "post-dial" state
-     * where Telecomm is now sending some dual-tone multi-frequency signaling (DTMF) tones appended
-     * to the dialed number. Normal transitions are to {@link #setPostDialWait(String,String)} when
-     * the post-dial string requires user confirmation to proceed, and {@link CallState#ACTIVE} when
-     * the post-dial tones are completed.
-     *
-     * @param callId The identifier of the call changing state.
-     * @param remaining The remaining postdial string to be dialed.
-     */
-    protected abstract void setPostDial(String callId, String remaining);
-
-    /**
-     * Indicates to the in-call app that the specified call was in the
-     * {@link #setPostDial(String,String)} state but is now waiting for user confirmation before the
-     * remaining digits can be sent. Normal transitions are to {@link #setPostDial(String,String)}
-     * when the user asks Telecomm to proceed with the post-dial sequence and the in-call app
-     * informs Telecomm of this by invoking {@link InCallAdapter#postDialContinue(String,boolean)}.
-     *
-     * @param callId The identifier of the call changing state.
-     * @param remaining The remaining postdial string to be dialed.
-     */
-    protected abstract void setPostDialWait(String callId, String remaining);
-
-    /**
-     * Called when the audio state changes.
-     *
-     * @param audioState The new {@link CallAudioState}.
-     */
-    protected abstract void onAudioStateChanged(CallAudioState audioState);
-
-    /**
-     * Brings the in-call screen to the foreground.
-     *
-     * @param showDialpad If true, put up the dialpad when the screen is shown.
-     */
-    protected abstract void bringToForeground(boolean showDialpad);
+    public void onPhoneDestroyed(Phone phone) { }
 }
diff --git a/telecomm/java/android/telecomm/Phone.java b/telecomm/java/android/telecomm/Phone.java
new file mode 100644
index 0000000..9c97b45
--- /dev/null
+++ b/telecomm/java/android/telecomm/Phone.java
@@ -0,0 +1,255 @@
+/*
+ * 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.telecomm;
+
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A unified virtual device providing a means of voice (and other) communication on a device.
+ */
+public final class Phone {
+
+    public abstract static class Listener {
+        /**
+         * Called when the audio state changes.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param audioState The new {@link CallAudioState}.
+         */
+        public void onAudioStateChanged(Phone phone, CallAudioState audioState) { }
+
+        /**
+         * Called to bring the in-call screen to the foreground. The in-call experience should
+         * respond immediately by coming to the foreground to inform the user of the state of
+         * ongoing {@code Call}s.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param showDialpad If true, put up the dialpad when the screen is shown.
+         */
+        public void onBringToForeground(Phone phone, boolean showDialpad) { }
+
+        /**
+         * Called when a {@code Call} has been added to this in-call session. The in-call user
+         * experience should add necessary state listeners to the specified {@code Call} and
+         * immediately start to show the user information about the existence
+         * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will
+         * include this {@code Call}.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param call A newly added {@code Call}.
+         */
+        public void onCallAdded(Phone phone, Call call) { }
+
+        /**
+         * Called when a {@code Call} has been removed from this in-call session. The in-call user
+         * experience should remove any state listeners from the specified {@code Call} and
+         * immediately stop displaying any information about this {@code Call}.
+         * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param call A newly removed {@code Call}.
+         */
+        public void onCallRemoved(Phone phone, Call call) { }
+    }
+
+    // A Map allows us to track each Call by its Telecomm-specified call ID
+    private final Map<String, Call> mCallByTelecommCallId = new ArrayMap<>();
+
+    // A List allows us to keep the Calls in a stable iteration order so that casually developed
+    // user interface components do not incur any spurious jank
+    private final List<Call> mCalls = new ArrayList<>();
+
+    // An unmodifiable view of the above List can be safely shared with subclass implementations
+    private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls);
+
+    private final InCallAdapter mInCallAdapter;
+
+    private CallAudioState mAudioState;
+
+    private final List<Listener> mListeners = new ArrayList<>();
+
+    /** {@hide} */
+    Phone(InCallAdapter adapter) {
+        mInCallAdapter = adapter;
+    }
+
+    /** {@hide} */
+    final void internalAddCall(InCallCall inCallCall) {
+        Call call = new Call(this, inCallCall.getId(), mInCallAdapter);
+        mCallByTelecommCallId.put(inCallCall.getId(), call);
+        mCalls.add(call);
+        checkCallTree(inCallCall);
+        call.internalUpdate(inCallCall);
+        fireCallAdded(call);
+     }
+
+    /** {@hide} */
+    final void internalRemoveCall(Call call) {
+        mCallByTelecommCallId.remove(call.internalGetCallId());
+        mCalls.remove(call);
+        fireCallRemoved(call);
+    }
+
+    /** {@hide} */
+    final void internalUpdateCall(InCallCall inCallCall) {
+         Call call = mCallByTelecommCallId.get(inCallCall.getId());
+         if (call != null) {
+             checkCallTree(inCallCall);
+             call.internalUpdate(inCallCall);
+         }
+     }
+
+    /** {@hide} */
+    final void internalSetPostDial(String callId, String remaining) {
+        Call call = mCallByTelecommCallId.get(callId);
+        if (call != null) {
+            call.internalSetPostDial(remaining);
+        }
+    }
+
+    /** {@hide} */
+    final void internalSetPostDialWait(String callId, String remaining) {
+        Call call = mCallByTelecommCallId.get(callId);
+        if (call != null) {
+            call.internalSetPostDialWait(remaining);
+        }
+    }
+
+    /** {@hide} */
+    final void internalAudioStateChanged(CallAudioState callAudioState) {
+        if (!Objects.equals(mAudioState, callAudioState)) {
+            mAudioState = callAudioState;
+            fireAudioStateChanged(callAudioState);
+        }
+    }
+
+    /** {@hide} */
+    final Call internalGetCallByTelecommId(String telecommId) {
+        return mCallByTelecommCallId.get(telecommId);
+    }
+
+    /** {@hide} */
+    final void internalBringToForeground(boolean showDialpad) {
+        fireBringToForeground(showDialpad);
+    }
+
+    /**
+     * Adds a listener to this {@code Phone}.
+     *
+     * @param listener A {@code Listener} object.
+     */
+    public final void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from this {@code Phone}.
+     *
+     * @param listener A {@code Listener} object.
+     */
+    public final void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Obtains the current list of {@code Call}s to be displayed by this in-call experience.
+     *
+     * @return A list of the relevant {@code Call}s.
+     */
+    public final List<Call> getCalls() {
+        return mUnmodifiableCalls;
+    }
+
+    /**
+     * Sets the microphone mute state. When this request is honored, there will be change to
+     * the {@link #getAudioState()}.
+     *
+     * @param state {@code true} if the microphone should be muted; {@code false} otherwise.
+     */
+    public final void setMuted(boolean state) {
+        mInCallAdapter.mute(state);
+    }
+
+    /**
+     * Sets the audio route (speaker, bluetooth, etc...).  When this request is honored, there will
+     * be change to the {@link #getAudioState()}.
+     *
+     * @param route The audio route to use.
+     */
+    public final void setAudioRoute(int route) {
+        mInCallAdapter.setAudioRoute(route);
+    }
+
+    /**
+     * Obtains the current phone call audio state of the {@code Phone}.
+     *
+     * @return An object encapsulating the audio state.
+     */
+    public final CallAudioState getAudioState() {
+        return mAudioState;
+    }
+
+    private void fireCallAdded(Call call) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onCallAdded(this, call);
+        }
+    }
+
+    private void fireCallRemoved(Call call) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onCallRemoved(this, call);
+        }
+    }
+
+    private void fireAudioStateChanged(CallAudioState audioState) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onAudioStateChanged(this, audioState);
+        }
+    }
+
+    private void fireBringToForeground(boolean showDialpad) {
+        Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].onBringToForeground(this, showDialpad);
+        }
+    }
+
+    private void checkCallTree(InCallCall inCallCall) {
+        if (inCallCall.getParentCallId() != null &&
+                !mCallByTelecommCallId.containsKey(inCallCall.getParentCallId())) {
+            Log.wtf(this, "InCallCall %s has nonexistent parent %s",
+                    inCallCall.getId(), inCallCall.getParentCallId());
+        }
+        if (inCallCall.getChildCallIds() != null) {
+            for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) {
+                if (!mCallByTelecommCallId.containsKey(inCallCall.getChildCallIds().get(i))) {
+                    Log.wtf(this, "InCallCall %s has nonexistent child %s",
+                            inCallCall.getId(), inCallCall.getChildCallIds().get(i));
+                }
+            }
+        }
+    }
+}
diff --git a/telecomm/java/android/telecomm/RemoteCallVideoProvider.java b/telecomm/java/android/telecomm/RemoteCallVideoProvider.java
index 856d321..a49076a 100644
--- a/telecomm/java/android/telecomm/RemoteCallVideoProvider.java
+++ b/telecomm/java/android/telecomm/RemoteCallVideoProvider.java
@@ -20,63 +20,92 @@
 import android.os.RemoteException;
 import android.view.Surface;
 
-import com.android.internal.telecomm.ICallVideoClient;
 import com.android.internal.telecomm.ICallVideoProvider;
 
-public class RemoteCallVideoProvider implements IBinder.DeathRecipient {
+public class RemoteCallVideoProvider {
     private final ICallVideoProvider mCallVideoProvider;
 
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
+        }
+    };
+
+    /** {@hide} */
     RemoteCallVideoProvider(ICallVideoProvider callVideoProvider) throws RemoteException {
         mCallVideoProvider = callVideoProvider;
-        mCallVideoProvider.asBinder().linkToDeath(this, 0);
+        mCallVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
     }
 
-    @Override
-    public void binderDied() {
-        mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
-    }
-
-    public void setCallVideoClient(CallVideoClient callVideoClient) throws RemoteException {
-        mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder());
+    public void setCallVideoClient(CallVideoClient callVideoClient) {
+        try {
+            mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder());
+        } catch (RemoteException e) {
+        }
     }
 
     public void setCamera(String cameraId) throws RemoteException {
         mCallVideoProvider.setCamera(cameraId);
     }
 
-    public void setPreviewSurface(Surface surface) throws RemoteException {
-        mCallVideoProvider.setPreviewSurface(surface);
+    public void setPreviewSurface(Surface surface) {
+        try {
+            mCallVideoProvider.setPreviewSurface(surface);
+        } catch (RemoteException e) {
+        }
     }
 
-    public void setDisplaySurface(Surface surface) throws RemoteException {
-        mCallVideoProvider.setDisplaySurface(surface);
+    public void setDisplaySurface(Surface surface) {
+        try {
+            mCallVideoProvider.setDisplaySurface(surface);
+        } catch (RemoteException e) {
+        }
     }
 
-    public void setDeviceOrientation(int rotation) throws RemoteException {
-        mCallVideoProvider.setDeviceOrientation(rotation);
+    public void setDeviceOrientation(int rotation) {
+        try {
+            mCallVideoProvider.setDeviceOrientation(rotation);
+        } catch (RemoteException e) {
+        }
     }
 
     public void setZoom(float value) throws RemoteException {
         mCallVideoProvider.setZoom(value);
     }
 
-    public void sendSessionModifyRequest(VideoCallProfile requestProfile) throws RemoteException {
-        mCallVideoProvider.sendSessionModifyRequest(requestProfile);
+    public void sendSessionModifyRequest(VideoCallProfile requestProfile) {
+        try {
+            mCallVideoProvider.sendSessionModifyRequest(requestProfile);
+        } catch (RemoteException e) {
+        }
     }
 
-    public void sendSessionModifyResponse(VideoCallProfile responseProfile) throws RemoteException {
-        mCallVideoProvider.sendSessionModifyResponse(responseProfile);
+    public void sendSessionModifyResponse(VideoCallProfile responseProfile) {
+        try {
+            mCallVideoProvider.sendSessionModifyResponse(responseProfile);
+        } catch (RemoteException e) {
+        }
     }
 
-    public void requestCameraCapabilities() throws RemoteException {
-        mCallVideoProvider.requestCameraCapabilities();
+    public void requestCameraCapabilities() {
+        try {
+            mCallVideoProvider.requestCameraCapabilities();
+        } catch (RemoteException e) {
+        }
     }
 
-    public void requestCallDataUsage() throws RemoteException {
-        mCallVideoProvider.requestCallDataUsage();
+    public void requestCallDataUsage() {
+        try {
+            mCallVideoProvider.requestCallDataUsage();
+        } catch (RemoteException e) {
+        }
     }
 
-    public void setPauseImage(String uri) throws RemoteException {
-        mCallVideoProvider.setPauseImage(uri);
+    public void setPauseImage(String uri) {
+        try {
+            mCallVideoProvider.setPauseImage(uri);
+        } catch (RemoteException e) {
+        }
     }
 }
\ No newline at end of file
diff --git a/telecomm/java/android/telecomm/RemoteConnectionManager.java b/telecomm/java/android/telecomm/RemoteConnectionManager.java
index 9cffdcc..0a0b245 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionManager.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionManager.java
@@ -54,9 +54,10 @@
         return accounts;
     }
 
-    public void createOutgoingConnection(
+    public void createRemoteConnection(
             ConnectionRequest request,
-            final ConnectionService.OutgoingCallResponse response) {
+            ConnectionService.CreateConnectionResponse response,
+            boolean isIncoming) {
         PhoneAccount account = request.getAccount();
         if (account == null) {
             throw new IllegalArgumentException("account must be specified.");
@@ -67,7 +68,7 @@
             throw new UnsupportedOperationException("account not supported: " + componentName);
         } else {
             RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
-            remoteService.createOutgoingConnection(request, response);
+            remoteService.createRemoteConnection(request, response, isIncoming);
         }
     }
 }
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
index 430133c..7fd8f93 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionService.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -44,40 +44,34 @@
 
     private String mConnectionId;
     private ConnectionRequest mPendingRequest;
-    private ConnectionService.OutgoingCallResponse<RemoteConnection> mPendingOutgoingCallResponse;
+    private ConnectionService.CreateConnectionResponse<RemoteConnection> mPendingResponse;
     // Remote connection services only support a single connection.
     private RemoteConnection mConnection;
 
     private final IConnectionServiceAdapter mAdapter = new IConnectionServiceAdapter.Stub() {
-
         @Override
-        public void notifyIncomingCall(ConnectionRequest request) {
-            Log.w(this, "notifyIncomingCall not implemented in Remote connection");
-        }
-
-        @Override
-        public void handleSuccessfulOutgoingCall(ConnectionRequest request) {
+        public void handleCreateConnectionSuccessful(ConnectionRequest request) {
             if (isPendingConnection(request.getCallId())) {
                 mConnection = new RemoteConnection(mConnectionService, request.getCallId());
-                mPendingOutgoingCallResponse.onSuccess(request, mConnection);
+                mPendingResponse.onSuccess(request, mConnection);
                 clearPendingInformation();
             }
         }
 
         @Override
-        public void handleFailedOutgoingCall(
+        public void handleCreateConnectionFailed(
                 ConnectionRequest request, int errorCode, String errorMessage) {
             if (isPendingConnection(request.getCallId())) {
-                mPendingOutgoingCallResponse.onFailure(request, errorCode, errorMessage);
+                mPendingResponse.onFailure(request, errorCode, errorMessage);
                 mConnectionId = null;
                 clearPendingInformation();
             }
         }
 
         @Override
-        public void cancelOutgoingCall(ConnectionRequest request) {
+        public void handleCreateConnectionCancelled(ConnectionRequest request) {
             if (isPendingConnection(request.getCallId())) {
-                mPendingOutgoingCallResponse.onCancel(request);
+                mPendingResponse.onCancel(request);
                 mConnectionId = null;
                 clearPendingInformation();
             }
@@ -226,12 +220,10 @@
         release();
     }
 
-    /**
-     * Places an outgoing call.
-     */
-    final void createOutgoingConnection(
+    final void createRemoteConnection(
             ConnectionRequest request,
-            ConnectionService.OutgoingCallResponse<RemoteConnection> response) {
+            ConnectionService.CreateConnectionResponse<RemoteConnection> response,
+            boolean isIncoming) {
 
         if (mConnectionId == null) {
             String id = UUID.randomUUID().toString();
@@ -243,9 +235,9 @@
                     request.getExtras(),
                     request.getVideoState());
             try {
-                mConnectionService.call(newRequest);
+                mConnectionService.createConnection(newRequest, isIncoming);
                 mConnectionId = id;
-                mPendingOutgoingCallResponse = response;
+                mPendingResponse = response;
                 mPendingRequest = request;
             } catch (RemoteException e) {
                 response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
@@ -255,9 +247,6 @@
         }
     }
 
-    // TODO(santoscordon): Handle incoming connections
-    // public final void handleIncomingConnection() {}
-
     final List<PhoneAccount> lookupAccounts(Uri handle) {
         // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
         // each time.
@@ -279,7 +268,7 @@
     }
 
     private boolean isPendingConnection(String id) {
-        return TextUtils.equals(mConnectionId, id) && mPendingOutgoingCallResponse != null;
+        return TextUtils.equals(mConnectionId, id) && mPendingResponse != null;
     }
 
     private boolean isCurrentConnection(String id) {
@@ -288,7 +277,7 @@
 
     private void clearPendingInformation() {
         mPendingRequest = null;
-        mPendingOutgoingCallResponse = null;
+        mPendingResponse = null;
     }
 
     private void destroyConnection() {
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index b9fb40c..a94841f 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -16,6 +16,7 @@
 
 package android.telecomm;
 
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.telephony.TelephonyManager;
@@ -31,9 +32,9 @@
      * to find and bind to the appropriate {@link android.telecomm.ConnectionService} which
      * Telecomm will ultimately use to control and get information about the call.</p>
      *
-     * <p>Input: get*Extra field {@link #EXTRA_CALL_SERVICE_DESCRIPTOR} contains the component name
-     * of the {@link android.telecomm.ConnectionService} that Telecomm should bind to. Telecomm
-     * will then ask the call service for more information about the call prior to showing any UI.
+     * <p>Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT} contains the component name of the
+     * {@link android.telecomm.ConnectionService} that Telecomm should bind to. Telecomm will then
+     * ask the connection service for more information about the call prior to showing any UI.
      *
      * TODO(santoscordon): Needs permissions.
      * TODO(santoscordon): Consider moving this into a simple method call on a system service.
@@ -41,16 +42,17 @@
     public static final String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL";
 
     /**
-     * The service action used to bind to {@link CallServiceProvider} implementations.
-     */
-    public static final String ACTION_CALL_SERVICE_PROVIDER = CallServiceProvider.class.getName();
-
-    /**
      * The service action used to bind to {@link ConnectionService} implementations.
      */
     public static final String ACTION_CONNECTION_SERVICE = ConnectionService.class.getName();
 
     /**
+     * The {@link Intent} action used to configure a {@link ConnectionService}.
+     */
+    public static final String ACTION_CONNECTION_SERVICE_CONFIGURE =
+            "android.intent.action.CONNECTION_SERVICE_CONFIGURE";
+
+    /**
      * Optional extra for {@link Intent#ACTION_CALL} containing a boolean that determines whether
      * the speakerphone should be automatically turned on for an outgoing call.
      */
@@ -69,11 +71,15 @@
             "android.intent.extra.START_CALL_WITH_VIDEO_STATE";
 
     /**
-     * Extra for {@link #ACTION_INCOMING_CALL} containing the {@link CallServiceDescriptor} that
-     * describes the call service to use for the incoming call.
+     * The extra used with an {@link android.content.Intent#ACTION_CALL},
+     * {@link #ACTION_INCOMING_CALL}, {@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_CALL_SERVICE_DESCRIPTOR =
-            "android.intent.extra.CALL_SERVICE_DESCRIPTOR";
+    public static final String EXTRA_PHONE_ACCOUNT = "android.intent.extra.PHONE_ACCOUNT";
 
     /**
      * Optional extra for {@link #ACTION_INCOMING_CALL} containing a {@link Bundle} which contains
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index fcd2eba..9d1e8f5 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -31,18 +31,6 @@
  * @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";
 
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl
deleted file mode 100644
index 10d73be..0000000
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telecomm;
-
-import android.os.IBinder;
-import android.telecomm.CallServiceDescriptor;
-import java.util.List;
-
-/**
- * Internal remote interface for call service lookup response.
- *
- * @see android.telecomm.CallServiceLookupResponse
- *
- * @hide
- */
-oneway interface ICallServiceLookupResponse {
-    void setCallServiceDescriptors(in List<CallServiceDescriptor> callServiceDescriptors);
-}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl
deleted file mode 100644
index 96daeed..0000000
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telecomm;
-
-import android.telecomm.CallServiceDescriptor;
-
-import com.android.internal.telecomm.ICallServiceLookupResponse;
-
-/**
- * Internal remote interface for call service providers.
- *
- * @see android.telecomm.CallServiceProvider
- *
- * @hide
- */
-oneway interface ICallServiceProvider {
-    void lookupCallServices(in ICallServiceLookupResponse response);
-}
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
index 16d2edf..9360219 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
@@ -32,12 +32,10 @@
 oneway interface IConnectionService {
     void addConnectionServiceAdapter(in IConnectionServiceAdapter adapter);
 
-    void call(in ConnectionRequest request);
+    void createConnection(in ConnectionRequest request, boolean isIncoming);
 
     void abort(String callId);
 
-    void createIncomingCall(in ConnectionRequest request);
-
     void answer(String callId);
 
     void reject(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
index bc67eab..b36f72c 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
@@ -31,13 +31,12 @@
  * {@hide}
  */
 oneway interface IConnectionServiceAdapter {
-    void notifyIncomingCall(in ConnectionRequest request);
+    void handleCreateConnectionSuccessful(in ConnectionRequest request);
 
-    void handleSuccessfulOutgoingCall(in ConnectionRequest request);
+    void handleCreateConnectionFailed(
+            in ConnectionRequest request, int errorCode, String errorMessage);
 
-    void handleFailedOutgoingCall(in ConnectionRequest request, int errorCode, String errorMessage);
-
-    void cancelOutgoingCall(in ConnectionRequest request);
+    void handleCreateConnectionCancelled(in ConnectionRequest request);
 
     void setActive(String callId);
 
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/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 4592717..d41ceda 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -650,7 +650,7 @@
      *
      * TODO: Add a link to documentation.
      *
-     * @return carrier privelege status defined in TelephonyManager.
+     * @return carrier privilege status defined in TelephonyManager.
      */
     int hasCarrierPrivileges();
 
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl b/telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl
deleted file mode 100644
index bcf2d81..0000000
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.IThirdPartyCallProvider;
-
-/**
- * Interface provided to ThirdPartyCallService. The service can use this to notify the listener of
- * changes to the call state.
- */
-oneway interface IThirdPartyCallListener {
-    /**
-     * Called by the service when a call provider is available to perform the outgoing or incoming
-     * call.
-     */
-    void onCallProviderAttached(IThirdPartyCallProvider callProvider);
-
-    /**
-     * Notifies the listener that ringing has started for this call.
-     */
-    void onRingingStarted();
-
-    /**
-     * Notifies the listener that the call has been successfully established.
-     */
-    void onCallEstablished();
-
-    /**
-     * Notifies the listener that the call has ended.
-     */
-    void onCallEnded(int reason);
-}
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl b/telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl
deleted file mode 100644
index 9d595b0..0000000
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.IThirdPartyCallListener;
-import com.android.internal.telephony.IThirdPartyCallSendDtmfCallback;
-
-/**
- * Interface sent to ThirdPartyCallListener.onCallProviderAttached. This is used to control an
- * outgoing or incoming call.
- */
-oneway interface IThirdPartyCallProvider {
-    /**
-     * Mutes or unmutes the call.
-     */
-    void mute(boolean shouldMute);
-
-    /**
-     * Ends the current call. If this is an unanswered incoming call then the call is rejected (for
-     * example, a notification is sent to a server that the user declined the call).
-     */
-    void hangup();
-
-    /**
-     * Accepts the incoming call.
-     */
-    void incomingCallAccept();
-
-    /**
-     * Sends the given DTMF code. The code can be '0'-'9', 'A'-'D', '#', or '*'.
-     */
-    void sendDtmf(char c, IThirdPartyCallSendDtmfCallback callback);
-}
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl b/telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl
deleted file mode 100644
index 597567a..0000000
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.IThirdPartyCallListener;
-
-/**
- * Interface provided by a service to start outgoing calls and attach to incoming calls.
- */
-oneway interface IThirdPartyCallService {
-    /**
-     * Call to start a new outgoing call.
-     */
-    void outgoingCallInitiate(IThirdPartyCallListener listener, String number);
-
-    /**
-     * Call to attach to an incoming call. This is in response to a call to
-     * TelephonyManager.newIncomingThirdPartyCall.
-     */
-    void incomingCallAttach(IThirdPartyCallListener listener, String callId);
-}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index a54936b..8a2732d 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -175,6 +175,11 @@
     }
 
     @Override
+    public File getNoBackupFilesDir() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public File getExternalFilesDir(String type) {
         throw new UnsupportedOperationException();
     }
@@ -190,6 +195,11 @@
     }
 
     @Override
+    public File getCodeCacheDir() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public File getExternalCacheDir() {
         throw new UnsupportedOperationException();
     }
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index a14714a..e388480 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -31,6 +31,7 @@
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
 import android.content.pm.ManifestDigest;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -616,6 +617,26 @@
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public KeySet getKeySetByAlias(String packageName, String alias) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public KeySet getSigningKeySet(String packageName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isSignedBy(String packageName, KeySet ks) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isSignedByExactly(String packageName, KeySet ks) {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * @hide
      */
@@ -729,7 +750,13 @@
     }
 
     /** {@hide} */
-    public PackageInstaller getPackageInstaller() {
+    public PackageInstaller getInstaller() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public boolean isPackageAvailable(String packageName) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/tests/OneMedia/Android.mk b/tests/OneMedia/Android.mk
index 93b9c9a..4feac68 100644
--- a/tests/OneMedia/Android.mk
+++ b/tests/OneMedia/Android.mk
@@ -10,8 +10,7 @@
 LOCAL_CERTIFICATE := platform
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-        android-support-v7-appcompat \
-        android-support-v7-mediarouter
+    android-support-media-protocols
 
 LOCAL_PROGUARD_ENABLED := disabled
 
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index 504d471..9d78ca5 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -27,11 +27,11 @@
             android:process="com.android.onemedia.service" />
         <service
             android:name=".provider.OneMediaRouteProvider"
-            android:permission="android.permission.BIND_ROUTE_PROVIDER"
+            android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE"
             android:exported="true"
             android:process="com.android.onemedia.provider">
             <intent-filter>
-                <action android:name="com.android.media.session.MediaRouteProvider" />
+                <action android:name="android.media.routing.MediaRouteService" />
             </intent-filter>
           </service>
     </application>
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
index ee407ad..894377b 100644
--- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
@@ -28,8 +28,6 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
-import com.android.onemedia.playback.Renderer;
-
 public class OnePlayerActivity extends Activity {
     private static final String TAG = "OnePlayerActivity";
 
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 145b389..9cbb455 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -18,13 +18,14 @@
 
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
-import android.media.session.RouteInfo;
+import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -44,7 +45,7 @@
     protected MediaController.TransportControls mTransportControls;
 
     private final Intent mServiceIntent;
-    private Context mContext;
+    private Activity mContext;
     private Listener mListener;
     private SessionCallback mControllerCb;
     private MediaSessionManager mManager;
@@ -52,7 +53,7 @@
 
     private boolean mResumed;
 
-    public PlayerController(Context context, Intent serviceIntent) {
+    public PlayerController(Activity context, Intent serviceIntent) {
         mContext = context;
         if (serviceIntent == null) {
             mServiceIntent = new Intent(mContext, PlayerService.class);
@@ -121,7 +122,7 @@
     }
 
     public void showRoutePicker() {
-        mController.showRoutePicker();
+        // TODO
     }
 
     private void unbindFromService() {
@@ -141,6 +142,7 @@
             mBinder = null;
             mController = null;
             mTransportControls = null;
+            mContext.setMediaController(null);
             Log.d(TAG, "Disconnected from PlayerService");
 
             if (mListener != null) {
@@ -152,12 +154,15 @@
         public void onServiceConnected(ComponentName name, IBinder service) {
             mBinder = IPlayerService.Stub.asInterface(service);
             Log.d(TAG, "service is " + service + " binder is " + mBinder);
+            MediaSession.Token token;
             try {
-                mController = MediaController.fromToken(mBinder.getSessionToken());
+                token = mBinder.getSessionToken();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error getting session", e);
                 return;
             }
+            mController = MediaController.fromToken(token);
+            mContext.setMediaController(mController);
             mController.addCallback(mControllerCb, mHandler);
             mTransportControls = mController.getTransportControls();
             Log.d(TAG, "Ready to use PlayerService");
@@ -173,11 +178,6 @@
 
     private class SessionCallback extends MediaController.Callback {
         @Override
-        public void onRouteChanged(RouteInfo route) {
-            // TODO
-        }
-
-        @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             if (state == null) {
                 return;
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index a220107..78353b2 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -17,14 +17,18 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.media.session.Route;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.media.session.RoutePlaybackControls;
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.MediaRouter;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.RouteInfo;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -34,11 +38,13 @@
 import com.android.onemedia.playback.RequestUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 
 public class PlayerSession {
     private static final String TAG = "PlayerSession";
 
     protected MediaSession mSession;
+    protected MediaRouter mRouter;
     protected Context mContext;
     protected Renderer mRenderer;
     protected MediaSession.Callback mCallback;
@@ -46,10 +52,6 @@
 
     protected PlaybackState mPlaybackState;
     protected Listener mListener;
-    protected ArrayList<RouteOptions> mRouteOptions;
-    protected Route mRoute;
-    protected RoutePlaybackControls mRouteControls;
-    protected RouteListener mRouteListener;
 
     private String mContent;
 
@@ -58,46 +60,58 @@
         mRenderer = new LocalRenderer(context, null);
         mCallback = new SessionCb();
         mRenderListener = new RenderListener();
-        mPlaybackState = new PlaybackState();
-        mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
-                | PlaybackState.ACTION_PLAY);
+        PlaybackState.Builder psBob = new PlaybackState.Builder();
+        psBob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY);
+        mPlaybackState = psBob.build();
 
         mRenderer.registerListener(mRenderListener);
-
-        // TODO need an easier way to build route options
-        mRouteOptions = new ArrayList<RouteOptions>();
-        RouteOptions.Builder bob = new RouteOptions.Builder();
-        bob.addInterface(RoutePlaybackControls.NAME);
-        mRouteOptions.add(bob.build());
-        mRouteListener = new RouteListener();
     }
 
     public void createSession() {
-        if (mSession != null) {
-            mSession.release();
-        }
+        releaseSession();
+
         MediaSessionManager man = (MediaSessionManager) mContext
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
+
+        mRouter = new MediaRouter(mContext);
+        mRouter.addSelector(new MediaRouteSelector.Builder()
+                .addRequiredProtocol(MediaPlayerProtocol.class)
+                .build());
+        mRouter.addSelector(new MediaRouteSelector.Builder()
+                .setRequiredFeatures(MediaRouter.ROUTE_FEATURE_LIVE_AUDIO)
+                .setOptionalFeatures(MediaRouter.ROUTE_FEATURE_LIVE_VIDEO)
+                .build());
+        mRouter.setRoutingCallback(new RoutingCallback(), null);
+
         mSession = man.createSession("OneMedia");
         mSession.addCallback(mCallback);
         mSession.addTransportControlsCallback(new TransportCallback());
         mSession.setPlaybackState(mPlaybackState);
         mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-        mSession.setRouteOptions(mRouteOptions);
+        mSession.setMediaRouter(mRouter);
         mSession.setActive(true);
     }
 
     public void onDestroy() {
-        if (mSession != null) {
-            mSession.release();
-        }
+        releaseSession();
         if (mRenderer != null) {
             mRenderer.unregisterListener(mRenderListener);
             mRenderer.onDestroy();
         }
     }
 
+    private void releaseSession() {
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+        if (mRouter != null) {
+            mRouter.release();
+            mRouter = null;
+        }
+    }
+
     public void setListener(Listener listener) {
         mListener = listener;
     }
@@ -118,7 +132,10 @@
     private void updateState(int newState) {
         float rate = newState == PlaybackState.STATE_PLAYING ? 1 : 0;
         long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
-        mPlaybackState.setState(newState, position, rate);
+        PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+        bob.setState(newState, position, rate, SystemClock.elapsedRealtime());
+        bob.setErrorMessage(null);
+        mPlaybackState = bob.build();
         mSession.setPlaybackState(mPlaybackState);
     }
 
@@ -131,10 +148,12 @@
         @Override
         public void onError(int type, int extra, Bundle extras, Throwable error) {
             Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
-            mPlaybackState.setState(PlaybackState.STATE_ERROR, -1, 0);
+            PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+            bob.setState(PlaybackState.STATE_ERROR, -1, 0, 0);
             if (error != null) {
-                mPlaybackState.setErrorMessage(error.getLocalizedMessage());
+                bob.setErrorMessage(error.getLocalizedMessage());
             }
+            mPlaybackState = bob.build();
             mSession.setPlaybackState(mPlaybackState);
             if (mListener != null) {
                 mListener.onPlayStateChanged(mPlaybackState);
@@ -143,36 +162,41 @@
 
         @Override
         public void onStateChanged(int newState) {
-            if (newState != Renderer.STATE_ERROR) {
-                mPlaybackState.setErrorMessage(null);
-            }
             long position = -1;
             if (mRenderer != null) {
                 position = mRenderer.getSeekPosition();
             }
+            int pbState;
+            float rate = 0;
+            String errorMsg = null;
             switch (newState) {
                 case Renderer.STATE_ENDED:
                 case Renderer.STATE_STOPPED:
-                    mPlaybackState.setState(PlaybackState.STATE_STOPPED, position, 0);
+                    pbState = PlaybackState.STATE_STOPPED;
                     break;
                 case Renderer.STATE_INIT:
                 case Renderer.STATE_PREPARING:
-                    mPlaybackState.setState(PlaybackState.STATE_BUFFERING, position, 0);
+                    pbState = PlaybackState.STATE_BUFFERING;
                     break;
                 case Renderer.STATE_ERROR:
-                    mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
+                    pbState = PlaybackState.STATE_ERROR;
                     break;
                 case Renderer.STATE_PAUSED:
-                    mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0);
+                    pbState = PlaybackState.STATE_PAUSED;
                     break;
                 case Renderer.STATE_PLAYING:
-                    mPlaybackState.setState(PlaybackState.STATE_PLAYING, position, 1);
+                    pbState = PlaybackState.STATE_PLAYING;
+                    rate = 1;
                     break;
                 default:
-                    mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
-                    mPlaybackState.setErrorMessage("unkown state");
+                    pbState = PlaybackState.STATE_ERROR;
+                    errorMsg = "unknown state";
                     break;
             }
+            PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+            bob.setState(pbState, position, rate, SystemClock.elapsedRealtime());
+            bob.setErrorMessage(errorMsg);
+            mPlaybackState = bob.build();
             mSession.setPlaybackState(mPlaybackState);
             if (mListener != null) {
                 mListener.onPlayStateChanged(mPlaybackState);
@@ -187,7 +211,10 @@
         public void onFocusLost() {
             Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
             long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
-            mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0);
+            PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+            bob.setState(PlaybackState.STATE_PAUSED, position, 0, SystemClock.elapsedRealtime());
+            bob.setErrorMessage(null);
+            mPlaybackState = bob.build();
             mSession.setPlaybackState(mPlaybackState);
             if (mListener != null) {
                 mListener.onPlayStateChanged(mPlaybackState);
@@ -218,40 +245,6 @@
                 }
             }
         }
-
-        @Override
-        public void onRequestRouteChange(RouteInfo route) {
-            if (mRenderer != null) {
-                mRenderer.onStop();
-            }
-            if (route == null) {
-                // Use local route
-                mRoute = null;
-                mRenderer = new LocalRenderer(mContext, null);
-                mRenderer.registerListener(mRenderListener);
-                updateState(PlaybackState.STATE_NONE);
-            } else {
-                // Use remote route
-                mSession.connect(route, mRouteOptions.get(0));
-                mRenderer = null;
-                updateState(PlaybackState.STATE_CONNECTING);
-            }
-        }
-
-        @Override
-        public void onRouteConnected(Route route) {
-            mRoute = route;
-            mRouteControls = RoutePlaybackControls.from(route);
-            mRouteControls.addListener(mRouteListener);
-            Log.d(TAG, "Connected to route, registering listener");
-            mRenderer = new OneMRPRenderer(mRouteControls);
-            updateState(PlaybackState.STATE_NONE);
-        }
-
-        @Override
-        public void onRouteDisconnected(Route route, int reason) {
-
-        }
     }
 
     private class TransportCallback extends MediaSession.TransportControlsCallback {
@@ -266,12 +259,62 @@
         }
     }
 
-    private class RouteListener extends RoutePlaybackControls.Listener {
+    private class RoutingCallback extends MediaRouter.RoutingCallback {
         @Override
-        public void onPlaybackStateChange(int state) {
-            Log.d(TAG, "Updating state to " + state);
-            updateState(state);
+        public void onConnectionStateChanged(int state) {
+            if (state == MediaRouter.CONNECTION_STATE_CONNECTING) {
+                if (mRenderer != null) {
+                    mRenderer.onStop();
+                }
+                mRenderer = null;
+                updateState(PlaybackState.STATE_CONNECTING);
+                return;
+            }
+
+            MediaRouter.ConnectionInfo connection = mRouter.getConnection();
+            if (connection != null) {
+                MediaPlayerProtocol protocol =
+                        connection.getProtocolObject(MediaPlayerProtocol.class);
+                if (protocol != null) {
+                    Log.d(TAG, "Connected to route using media player protocol");
+
+                    protocol.setCallback(new PlayerCallback(), null);
+                    mRenderer = new OneMRPRenderer(protocol);
+                    updateState(PlaybackState.STATE_NONE);
+                    return;
+                }
+            }
+
+            // Use local route
+            mRenderer = new LocalRenderer(mContext, null);
+            mRenderer.registerListener(mRenderListener);
+            updateState(PlaybackState.STATE_NONE);
         }
     }
 
+    private class PlayerCallback extends MediaPlayerProtocol.Callback {
+        @Override
+        public void onStatusUpdated(MediaStatus status, Bundle extras) {
+            if (status != null) {
+                Log.d(TAG, "Received status update: " + status.toBundle());
+                switch (status.getPlayerState()) {
+                    case MediaStatus.PLAYER_STATE_BUFFERING:
+                        updateState(PlaybackState.STATE_BUFFERING);
+                        break;
+                    case MediaStatus.PLAYER_STATE_IDLE:
+                        updateState(PlaybackState.STATE_STOPPED);
+                        break;
+                    case MediaStatus.PLAYER_STATE_PAUSED:
+                        updateState(PlaybackState.STATE_PAUSED);
+                        break;
+                    case MediaStatus.PLAYER_STATE_PLAYING:
+                        updateState(PlaybackState.STATE_PLAYING);
+                        break;
+                    case MediaStatus.PLAYER_STATE_UNKNOWN:
+                        updateState(PlaybackState.STATE_NONE);
+                        break;
+                }
+            } 
+        }
+    }
 }
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
index 05516d2..c133325 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
@@ -15,10 +15,10 @@
  */
 package com.android.onemedia.playback;
 
+import android.media.MediaMetadata;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.support.v7.media.MediaItemMetadata;
 
 /**
  * TODO: Insert description here. (generated by epastern)
@@ -35,11 +35,11 @@
     }
 
     public String getTitle() {
-        return mBundle.getString(MediaItemMetadata.KEY_TITLE);
+        return mBundle.getString(MediaMetadata.METADATA_KEY_TITLE);
     }
 
     public String getArtist() {
-        return mBundle.getString(MediaItemMetadata.KEY_ALBUM_ARTIST);
+        return mBundle.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST);
     }
 
     /* (non-Javadoc)
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
index 9b0a2b2..55eb92c 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
@@ -1,39 +1,42 @@
 package com.android.onemedia.playback;
 
-import android.media.session.RoutePlaybackControls;
 import android.os.Bundle;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaInfo;
 
 /**
  * Renderer for communicating with the OneMRP route
  */
 public class OneMRPRenderer extends Renderer {
-    private final RoutePlaybackControls mControls;
+    private final MediaPlayerProtocol mProtocol;
 
-    public OneMRPRenderer(RoutePlaybackControls controls) {
+    public OneMRPRenderer(MediaPlayerProtocol protocol) {
         super(null, null);
-        mControls = controls;
+        mProtocol = protocol;
     }
 
     @Override
     public void setContent(Bundle request) {
-        mControls.playNow(request.getString(RequestUtils.EXTRA_KEY_SOURCE));
+        MediaInfo mediaInfo = new MediaInfo(request.getString(RequestUtils.EXTRA_KEY_SOURCE),
+                MediaInfo.STREAM_TYPE_BUFFERED, "audio/mp3");
+        mProtocol.load(mediaInfo, true, 0, null);
     }
 
     @Override
     public boolean onStop() {
-        mControls.pause();
+        mProtocol.stop(null);
         return true;
     }
 
     @Override
     public boolean onPlay() {
-        mControls.resume();
+        mProtocol.play(null);
         return true;
     }
 
     @Override
     public boolean onPause() {
-        mControls.pause();
+        mProtocol.pause(null);
         return true;
     }
 
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
index dd0d982..3778c5f 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
@@ -16,7 +16,6 @@
 package com.android.onemedia.playback;
 
 import android.os.Bundle;
-import android.support.v7.media.MediaItemMetadata;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
index f2d691c..5845e48 100644
--- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
+++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
@@ -15,19 +15,23 @@
  */
 package com.android.onemedia.provider;
 
-import android.media.routeprovider.RouteConnection;
-import android.media.routeprovider.RouteInterfaceHandler;
-import android.media.routeprovider.RoutePlaybackControlsHandler;
-import android.media.routeprovider.RouteProviderService;
-import android.media.routeprovider.RouteRequest;
-import android.media.session.RouteInfo;
-import android.media.session.RoutePlaybackControls;
-import android.media.session.RouteInterface;
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.MediaRouteService;
+import android.media.routing.MediaRouter.ConnectionInfo;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.DiscoveryRequest;
+import android.media.routing.MediaRouter.RouteInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Process;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaInfo;
+import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
 import android.os.Looper;
 import android.os.ResultReceiver;
+import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.onemedia.playback.LocalRenderer;
@@ -35,111 +39,135 @@
 import com.android.onemedia.playback.RequestUtils;
 
 import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
 
 /**
  * Test of MediaRouteProvider. Show a dummy provider with a simple interface for
  * playing music.
  */
-public class OneMediaRouteProvider extends RouteProviderService {
+public class OneMediaRouteProvider extends MediaRouteService {
     private static final String TAG = "OneMRP";
     private static final boolean DEBUG = true;
 
+    private static final String TEST_DESTINATION_ID = "testDestination";
+    private static final String TEST_ROUTE_ID = "testRoute";
+
     private Renderer mRenderer;
     private RenderListener mRenderListener;
     private PlaybackState mPlaybackState;
-    private RouteConnection mConnection;
-    private RoutePlaybackControlsHandler mControls;
-    private String mRouteId;
     private Handler mHandler;
 
+    private OneStub mStub;
+
     @Override
     public void onCreate() {
         mHandler = new Handler();
-        mRouteId = UUID.randomUUID().toString();
         mRenderer = new LocalRenderer(this, null);
         mRenderListener = new RenderListener();
-        mPlaybackState = new PlaybackState();
-        mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
-                | PlaybackState.ACTION_PLAY);
+        PlaybackState.Builder bob = new PlaybackState.Builder();
+        bob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY);
+        mPlaybackState = bob.build();
 
         mRenderer.registerListener(mRenderListener);
-
-        if (DEBUG) {
-            Log.d(TAG, "onCreate, routeId is " + mRouteId);
-        }
     }
 
     @Override
-    public List<RouteInfo> getMatchingRoutes(List<RouteRequest> requests) {
-        RouteInfo.Builder bob = new RouteInfo.Builder();
-        bob.setName("OneMedia").setId(mRouteId);
-        // TODO add a helper library for generating route info with the correct
-        // options
-        Log.d(TAG, "Requests:");
-        for (RouteRequest request : requests) {
-            List<String> ifaces = request.getConnectionOptions().getInterfaceNames();
-            Log.d(TAG, "  request ifaces:" + ifaces.toString());
-            if (ifaces != null && ifaces.size() == 1
-                    && RoutePlaybackControls.NAME.equals(ifaces.get(0))) {
-                bob.addRouteOptions(request.getConnectionOptions());
-            }
+    public ClientSession onCreateClientSession(ClientInfo client) {
+        if (client.getUid() != Process.myUid()) {
+            // for testing purposes, only allow connections from this application
+            // since this provider is not fully featured
+            return null;
         }
-        ArrayList<RouteInfo> result = new ArrayList<RouteInfo>();
-        if (bob.getOptionsSize() > 0) {
-            RouteInfo info = bob.build();
-            result.add(info);
-        }
-        if (DEBUG) {
-            Log.d(TAG, "getRoutes returning " + result.toString());
-        }
-        return result;
+        return new OneSession(client);
     }
 
-    @Override
-    public RouteConnection connect(RouteInfo route, RouteRequest request) {
-        if (mConnection != null) {
-            disconnect(mConnection);
-        }
-        RouteConnection connection = new RouteConnection(this, route);
-        mControls = RoutePlaybackControlsHandler.addTo(connection);
-        mControls.addListener(new PlayHandler(mRouteId), mHandler);
-        if (DEBUG) {
-            Log.d(TAG, "Connected to route");
-        }
-        return connection;
-    }
+    private final class OneSession extends ClientSession {
+        private final ClientInfo mClient;
 
-    private class PlayHandler extends RoutePlaybackControlsHandler.Listener {
-        private final String mRouteId;
-
-        public PlayHandler(String routeId) {
-            mRouteId = routeId;
+        public OneSession(ClientInfo client) {
+            mClient = client;
         }
 
         @Override
-        public void playNow(String content, ResultReceiver cb) {
+        public boolean onStartDiscovery(DiscoveryRequest req, DiscoveryCallback callback) {
+            for (MediaRouteSelector selector : req.getSelectors()) {
+                if (isMatch(selector)) {
+                    DestinationInfo destination = new DestinationInfo.Builder(
+                            TEST_DESTINATION_ID, getServiceMetadata(), "OneMedia")
+                            .setDescription("Test route from OneMedia app.")
+                            .build();
+                    ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>();
+                    routes.add(new RouteInfo.Builder(
+                            TEST_ROUTE_ID, destination, selector).build());
+                    callback.onDestinationFound(destination, routes);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void onStopDiscovery() {
+        }
+
+        @Override
+        public boolean onConnect(ConnectionRequest req, ConnectionCallback callback) {
+            if (req.getRoute().getId().equals(TEST_ROUTE_ID)) {
+                mStub = new OneStub();
+                ConnectionInfo connection = new ConnectionInfo.Builder(req.getRoute())
+                        .setProtocolStub(MediaPlayerProtocol.class, mStub)
+                        .build();
+                callback.onConnected(connection);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onDisconnect() {
+            mStub = null;
+        }
+
+        private boolean isMatch(MediaRouteSelector selector) {
+            if (!selector.containsProtocol(MediaPlayerProtocol.class)) {
+                return false;
+            }
+            for (String protocol : selector.getRequiredProtocols()) {
+                if (!protocol.equals(MediaPlayerProtocol.class.getName())) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private final class OneStub extends MediaPlayerProtocol.Stub {
+        MediaInfo mMediaInfo;
+
+        public OneStub() {
+            super(mHandler);
+        }
+
+        @Override
+        public void onLoad(MediaInfo mediaInfo, boolean autoplay, long playPosition,
+                Bundle extras) {
             if (DEBUG) {
-                Log.d(TAG, "Attempting to play " + content);
+                Log.d(TAG, "Attempting to play " + mediaInfo.getContentId());
             }
             // look up the route and send a play command to it
+            mMediaInfo = mediaInfo;
             Bundle bundle = new Bundle();
-            bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, content);
+            bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, mediaInfo.getContentId());
             mRenderer.setContent(bundle);
-            RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS, null);
         }
 
         @Override
-        public boolean resume() {
+        public void onPlay(Bundle extras) {
             mRenderer.onPlay();
-            return true;
         }
 
         @Override
-        public boolean pause() {
+        public void onPause(Bundle extras) {
             mRenderer.onPause();
-            return true;
         }
     }
 
@@ -148,45 +176,48 @@
         @Override
         public void onError(int type, int extra, Bundle extras, Throwable error) {
             Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
-            if (mControls != null) {
-                mControls.sendPlaybackChangeEvent(PlaybackState.STATE_ERROR);
-            }
+            sendStatusUpdate(PlaybackState.STATE_ERROR);
         }
 
         @Override
         public void onStateChanged(int newState) {
-            if (newState != Renderer.STATE_ERROR) {
-                mPlaybackState.setErrorMessage(null);
-            }
             long position = -1;
             if (mRenderer != null) {
                 position = mRenderer.getSeekPosition();
             }
+            int pbState;
+            float rate = 0;
+            String errorMsg = null;
             switch (newState) {
                 case Renderer.STATE_ENDED:
                 case Renderer.STATE_STOPPED:
-                    mPlaybackState.setState(PlaybackState.STATE_STOPPED, position, 0);
+                    pbState = PlaybackState.STATE_STOPPED;
                     break;
                 case Renderer.STATE_INIT:
                 case Renderer.STATE_PREPARING:
-                    mPlaybackState.setState(PlaybackState.STATE_BUFFERING, position, 0);
+                    pbState = PlaybackState.STATE_BUFFERING;
                     break;
                 case Renderer.STATE_ERROR:
-                    mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
+                    pbState = PlaybackState.STATE_ERROR;
                     break;
                 case Renderer.STATE_PAUSED:
-                    mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0);
+                    pbState = PlaybackState.STATE_PAUSED;
                     break;
                 case Renderer.STATE_PLAYING:
-                    mPlaybackState.setState(PlaybackState.STATE_PLAYING, position, 1);
+                    pbState = PlaybackState.STATE_PLAYING;
+                    rate = 1;
                     break;
                 default:
-                    mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
-                    mPlaybackState.setErrorMessage("unkown state");
+                    pbState = PlaybackState.STATE_ERROR;
+                    errorMsg = "unknown state";
                     break;
             }
+            PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+            bob.setState(pbState, position, rate, SystemClock.elapsedRealtime());
+            bob.setErrorMessage(errorMsg);
+            mPlaybackState = bob.build();
 
-            mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
+            sendStatusUpdate(mPlaybackState.getState());
         }
 
         @Override
@@ -195,13 +226,45 @@
 
         @Override
         public void onFocusLost() {
-            Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
-            mPlaybackState.setState(PlaybackState.STATE_PAUSED, mRenderer.getSeekPosition(), 0);
+            Log.d(TAG, "Focus lost, pausing");
+            // Don't update state here, we'll get a separate call to
+            // onStateChanged when it pauses
             mRenderer.onPause();
         }
 
         @Override
         public void onNextStarted() {
         }
+
+        private void sendStatusUpdate(int state) {
+            if (mStub != null) {
+                MediaStatus status = new MediaStatus(1, mStub.mMediaInfo);
+                switch (state) {
+                    case PlaybackState.STATE_BUFFERING:
+                    case PlaybackState.STATE_FAST_FORWARDING:
+                    case PlaybackState.STATE_REWINDING:
+                    case PlaybackState.STATE_SKIPPING_TO_NEXT:
+                    case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_BUFFERING);
+                        break;
+                    case PlaybackState.STATE_CONNECTING:
+                    case PlaybackState.STATE_STOPPED:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_IDLE);
+                        break;
+                    case PlaybackState.STATE_PAUSED:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_PAUSED);
+                        break;
+                    case PlaybackState.STATE_PLAYING:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_PLAYING);
+                        break;
+                    case PlaybackState.STATE_NONE:
+                    case PlaybackState.STATE_ERROR:
+                    default:
+                        status.setPlayerState(MediaStatus.PLAYER_STATE_UNKNOWN);
+                        break;
+                }
+                mStub.sendStatusUpdatedEvent(status, null);
+            }
+        }
     }
 }
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/Images.cpp b/tools/aapt/Images.cpp
index 12f5b92..28de933 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -77,6 +77,14 @@
     int32_t layoutBoundsRight;
     int32_t layoutBoundsBottom;
 
+    // Round rect outline description
+    int32_t outlineInsetsLeft;
+    int32_t outlineInsetsTop;
+    int32_t outlineInsetsRight;
+    int32_t outlineInsetsBottom;
+    float outlineRadius;
+    bool outlineFilled;
+
     png_uint_32 allocHeight;
     png_bytepp allocRows;
 };
@@ -397,6 +405,98 @@
     return NO_ERROR;
 }
 
+static void find_max_opacity(png_byte** rows,
+                             int startX, int startY, int endX, int endY, int dX, int dY,
+                             int* out_inset)
+{
+    bool opaque_within_inset = true;
+    unsigned char max_opacity = 0;
+    int inset = 0;
+    *out_inset = 0;
+    for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+        png_byte* color = rows[y] + x * 4;
+        unsigned char opacity = color[3];
+        if (opacity > max_opacity) {
+            max_opacity = opacity;
+            *out_inset = inset;
+        }
+        if (opacity == 0xff) return;
+    }
+}
+
+static bool is_opaque_over_row(png_byte* row, int startX, int endX)
+{
+    for (int x = startX; x < endX; x++) {
+        png_byte* color = row + x * 4;
+        if (color[3] != 0xff) return false;
+    }
+    return true;
+}
+
+static bool is_opaque_over_col(png_byte** rows, int offsetX, int startY, int endY)
+{
+    for (int y = startY; y < endY; y++) {
+        png_byte* color = rows[y] + offsetX * 4;
+        if (color[3] != 0xff) return false;
+    }
+    return true;
+}
+
+static void get_outline(image_info* image)
+{
+    int midX = image->width / 2;
+    int midY = image->height / 2;
+    int endX = image->width - 2;
+    int endY = image->height - 2;
+
+    // find left and right extent of nine patch content on center row
+    if (image->width > 4) {
+        find_max_opacity(image->rows, 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+        find_max_opacity(image->rows, endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
+    } else {
+        image->outlineInsetsLeft = 0;
+        image->outlineInsetsRight = 0;
+    }
+
+    // find top and bottom extent of nine patch content on center column
+    if (image->height > 4) {
+        find_max_opacity(image->rows, midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+        find_max_opacity(image->rows, midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
+    } else {
+        image->outlineInsetsTop = 0;
+        image->outlineInsetsBottom = 0;
+    }
+
+    int innerStartX = 1 + image->outlineInsetsLeft;
+    int innerStartY = 1 + image->outlineInsetsTop;
+    int innerEndX = endX - image->outlineInsetsRight;
+    int innerEndY = endY - image->outlineInsetsBottom;
+    int innerMidX = (innerEndX + innerStartX) / 2;
+    int innerMidY = (innerEndY + innerStartY) / 2;
+
+    // assuming the image is a round rect, compute the radius by marching
+    // diagonally from the top left corner towards the center
+    image->outlineFilled = is_opaque_over_row(image->rows[innerMidY], innerStartX, innerEndX)
+            && is_opaque_over_col(image->rows, innerMidX, innerStartY, innerStartY);
+
+    int diagonalInset = 0;
+    find_max_opacity(image->rows, innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+            &diagonalInset);
+
+    // Determine source radius based upon inset
+    // radius = 1 / (sqrt(2) - 1) * inset
+    image->outlineRadius = 2.4142f * diagonalInset;
+
+    NOISY(printf("outline insets %d %d %d %d, rad %f, filled %d\n",
+            image->outlineFilled,
+            image->outlineInsetsLeft,
+            image->outlineInsetsTop,
+            image->outlineInsetsRight,
+            image->outlineInsetsBottom,
+            image->outlineRadius,
+            image->outlineFilled));
+}
+
 
 static uint32_t get_color(
     png_bytepp rows, int left, int top, int right, int bottom)
@@ -571,6 +671,9 @@
                 image->layoutBoundsRight, image->layoutBoundsBottom));
     }
 
+    // use opacity of pixels to estimate the round rect outline
+    get_outline(image);
+
     // If padding is not yet specified, take values from size.
     if (image->info9Patch.paddingLeft < 0) {
         image->info9Patch.paddingLeft = xDivs[0];
@@ -966,9 +1069,10 @@
     int bit_depth, interlace_type, compression_type;
     int i;
 
-    png_unknown_chunk unknowns[2];
+    png_unknown_chunk unknowns[3];
     unknowns[0].data = NULL;
     unknowns[1].data = NULL;
+    unknowns[2].data = NULL;
 
     png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
     if (outRows == (png_bytepp) 0) {
@@ -1038,12 +1142,17 @@
     }
 
     if (imageInfo.is9Patch) {
-        int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0);
-        int p_index = imageInfo.haveLayoutBounds ? 1 : 0;
-        int b_index = 0;
+        int chunk_count = 2 + (imageInfo.haveLayoutBounds ? 1 : 0);
+        int p_index = imageInfo.haveLayoutBounds ? 2 : 1;
+        int b_index = 1;
+        int o_index = 0;
+
+        // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
         png_byte *chunk_names = imageInfo.haveLayoutBounds
-                ? (png_byte*)"npLb\0npTc\0"
-                : (png_byte*)"npTc";
+                ? (png_byte*)"npOl\0npLb\0npTc\0"
+                : (png_byte*)"npOl\0npTc";
+
+        // base 9 patch data
         NOISY(printf("Adding 9-patch info...\n"));
         strcpy((char*)unknowns[p_index].name, "npTc");
         unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
@@ -1051,6 +1160,18 @@
         // TODO: remove the check below when everything works
         checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
 
+        // automatically generated 9 patch outline data
+        int chunk_size = sizeof(png_uint_32) * 6;
+        strcpy((char*)unknowns[o_index].name, "npOl");
+        unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
+        png_byte outputData[chunk_size];
+        memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
+        ((float*) outputData)[4] = imageInfo.outlineRadius;
+        ((png_uint_32*) outputData)[5] = imageInfo.outlineFilled ? 1 : 0;
+        memcpy(unknowns[o_index].data, &outputData, chunk_size);
+        unknowns[o_index].size = chunk_size;
+
+        // optional optical inset / layout bounds data
         if (imageInfo.haveLayoutBounds) {
             int chunk_size = sizeof(png_uint_32) * 4;
             strcpy((char*)unknowns[b_index].name, "npLb");
@@ -1099,6 +1220,7 @@
     free(outRows);
     free(unknowns[0].data);
     free(unknowns[1].data);
+    free(unknowns[2].data);
 
     png_get_IHDR(write_ptr, write_info, &width, &height,
        &bit_depth, &color_type, &interlace_type,
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 963c7962..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 efd55bf..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;
@@ -1083,6 +1089,12 @@
     }
 
     @Override
+    public File getNoBackupFilesDir() {
+        // pass
+        return null;
+    }
+
+    @Override
     public File getExternalFilesDir(String type) {
         // 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);
+    }
 }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 22ba924..292f1e8 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -41,6 +41,8 @@
 
     List<WifiConfiguration> getConfiguredNetworks();
 
+    List<WifiConfiguration> getPrivilegedConfiguredNetworks();
+
     int addOrUpdateNetwork(in WifiConfiguration config);
 
     boolean removeNetwork(int netId);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7ad05ab..8945e52 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -629,6 +629,16 @@
         }
     }
 
+    /** @hide */
+    @SystemApi
+    public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
+        try {
+            return mService.getPrivilegedConfiguredNetworks();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
     /**
      * Add a new network description to the set of configured networks.
      * The {@code networkId} field of the supplied configuration object