Merge "Unhide Build.SUPPORTED_ABIS and friends."
diff --git a/Android.mk b/Android.mk
index 5d957d0..589279d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -130,6 +130,7 @@
 	core/java/android/content/pm/IPackageInstallObserver.aidl \
 	core/java/android/content/pm/IPackageInstallObserver2.aidl \
 	core/java/android/content/pm/IPackageInstaller.aidl \
+	core/java/android/content/pm/IPackageInstallerObserver.aidl \
 	core/java/android/content/pm/IPackageInstallerSession.aidl \
 	core/java/android/content/pm/IPackageManager.aidl \
 	core/java/android/content/pm/IPackageMoveObserver.aidl \
@@ -156,6 +157,9 @@
 	core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl \
 	core/java/android/hardware/input/IInputManager.aidl \
 	core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
+	core/java/android/hardware/location/IActivityRecognitionHardware.aidl \
+	core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl \
+	core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl \
 	core/java/android/hardware/location/IFusedLocationHardware.aidl \
 	core/java/android/hardware/location/IFusedLocationHardwareSink.aidl \
 	core/java/android/hardware/location/IGeofenceHardware.aidl \
@@ -288,6 +292,7 @@
 	location/java/android/location/IFusedProvider.aidl \
 	location/java/android/location/IGeocodeProvider.aidl \
 	location/java/android/location/IGeofenceProvider.aidl \
+	location/java/android/location/IGpsMeasurementsListener.aidl \
 	location/java/android/location/IGpsStatusListener.aidl \
 	location/java/android/location/IGpsStatusProvider.aidl \
 	location/java/android/location/ILocationListener.aidl \
@@ -342,11 +347,13 @@
 	telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl \
 	telephony/java/com/android/ims/internal/IImsCallSession.aidl \
 	telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl \
+	telephony/java/com/android/ims/internal/IImsConfig.aidl \
 	telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl \
 	telephony/java/com/android/ims/internal/IImsService.aidl \
 	telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
 	telephony/java/com/android/ims/internal/IImsUt.aidl \
 	telephony/java/com/android/ims/internal/IImsUtListener.aidl \
+	telephony/java/com/android/ims/ImsConfigListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/api/current.txt b/api/current.txt
index 4846d32..f5cc80c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5146,6 +5146,11 @@
     method public void onCommandResult(android.os.Bundle);
   }
 
+  public static class VoiceInteractor.CompleteVoiceRequest extends android.app.VoiceInteractor.Request {
+    ctor public VoiceInteractor.CompleteVoiceRequest(java.lang.CharSequence, android.os.Bundle);
+    method public void onCompleteResult(android.os.Bundle);
+  }
+
   public static class VoiceInteractor.ConfirmationRequest extends android.app.VoiceInteractor.Request {
     ctor public VoiceInteractor.ConfirmationRequest(java.lang.CharSequence, android.os.Bundle);
     method public void onConfirmationResult(boolean, android.os.Bundle);
@@ -5718,9 +5723,9 @@
     method public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(java.lang.String, java.util.UUID) throws java.io.IOException;
     method public boolean setName(java.lang.String);
     method public boolean startDiscovery();
-    method public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
-    method public boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
-    method public void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+    method public deprecated boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+    method public deprecated boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
+    method public deprecated void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
     field public static final java.lang.String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
     field public static final java.lang.String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
     field public static final java.lang.String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
@@ -6402,44 +6407,16 @@
 
   public abstract class AdvertiseCallback {
     ctor public AdvertiseCallback();
-    method public abstract void onFailure(int);
-    method public abstract void onSuccess(android.bluetooth.le.AdvertiseSettings);
+    method public void onStartFailure(int);
+    method public void onStartSuccess(android.bluetooth.le.AdvertiseSettings);
     field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3
-    field public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5; // 0x5
-    field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 7; // 0x7
-    field public static final int ADVERTISE_FAILED_NOT_STARTED = 4; // 0x4
+    field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
+    field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; // 0x4
     field public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1; // 0x1
     field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2
   }
 
-  public final class AdvertiseSettings implements android.os.Parcelable {
-    method public int describeContents();
-    method public int getMode();
-    method public int getTxPowerLevel();
-    method public int getType();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final int ADVERTISE_MODE_BALANCED = 1; // 0x1
-    field public static final int ADVERTISE_MODE_LOW_LATENCY = 2; // 0x2
-    field public static final int ADVERTISE_MODE_LOW_POWER = 0; // 0x0
-    field public static final int ADVERTISE_TX_POWER_HIGH = 3; // 0x3
-    field public static final int ADVERTISE_TX_POWER_LOW = 1; // 0x1
-    field public static final int ADVERTISE_TX_POWER_MEDIUM = 2; // 0x2
-    field public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; // 0x0
-    field public static final int ADVERTISE_TYPE_CONNECTABLE = 2; // 0x2
-    field public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; // 0x0
-    field public static final int ADVERTISE_TYPE_SCANNABLE = 1; // 0x1
-    field public static final android.os.Parcelable.Creator CREATOR;
-  }
-
-  public static final class AdvertiseSettings.Builder {
-    ctor public AdvertiseSettings.Builder();
-    method public android.bluetooth.le.AdvertiseSettings build();
-    method public android.bluetooth.le.AdvertiseSettings.Builder setAdvertiseMode(int);
-    method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
-    method public android.bluetooth.le.AdvertiseSettings.Builder setType(int);
-  }
-
-  public final class AdvertisementData implements android.os.Parcelable {
+  public final class AdvertiseData implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getIncludeTxPowerLevel();
     method public int getManufacturerId();
@@ -6451,43 +6428,69 @@
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
-  public static final class AdvertisementData.Builder {
-    ctor public AdvertisementData.Builder();
-    method public android.bluetooth.le.AdvertisementData build();
-    method public android.bluetooth.le.AdvertisementData.Builder setIncludeTxPowerLevel(boolean);
-    method public android.bluetooth.le.AdvertisementData.Builder setManufacturerData(int, byte[]);
-    method public android.bluetooth.le.AdvertisementData.Builder setServiceData(android.os.ParcelUuid, byte[]);
-    method public android.bluetooth.le.AdvertisementData.Builder setServiceUuids(java.util.List<android.os.ParcelUuid>);
+  public static final class AdvertiseData.Builder {
+    ctor public AdvertiseData.Builder();
+    method public android.bluetooth.le.AdvertiseData build();
+    method public android.bluetooth.le.AdvertiseData.Builder setIncludeTxPowerLevel(boolean);
+    method public android.bluetooth.le.AdvertiseData.Builder setManufacturerData(int, byte[]);
+    method public android.bluetooth.le.AdvertiseData.Builder setServiceData(android.os.ParcelUuid, byte[]);
+    method public android.bluetooth.le.AdvertiseData.Builder setServiceUuids(java.util.List<android.os.ParcelUuid>);
+  }
+
+  public final class AdvertiseSettings implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean getIsConnectable();
+    method public int getMode();
+    method public int getTimeout();
+    method public int getTxPowerLevel();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int ADVERTISE_MODE_BALANCED = 1; // 0x1
+    field public static final int ADVERTISE_MODE_LOW_LATENCY = 2; // 0x2
+    field public static final int ADVERTISE_MODE_LOW_POWER = 0; // 0x0
+    field public static final int ADVERTISE_TX_POWER_HIGH = 3; // 0x3
+    field public static final int ADVERTISE_TX_POWER_LOW = 1; // 0x1
+    field public static final int ADVERTISE_TX_POWER_MEDIUM = 2; // 0x2
+    field public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; // 0x0
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public static final class AdvertiseSettings.Builder {
+    ctor public AdvertiseSettings.Builder();
+    method public android.bluetooth.le.AdvertiseSettings build();
+    method public android.bluetooth.le.AdvertiseSettings.Builder setAdvertiseMode(int);
+    method public android.bluetooth.le.AdvertiseSettings.Builder setIsConnectable(boolean);
+    method public android.bluetooth.le.AdvertiseSettings.Builder setTimeout(int);
+    method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
   }
 
   public final class BluetoothLeAdvertiser {
-    method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertiseCallback);
-    method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertiseCallback);
+    method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+    method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
     method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
   }
 
   public final class BluetoothLeScanner {
+    method public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
+    method public void startScan(android.bluetooth.le.ScanCallback);
     method public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
     method public void stopScan(android.bluetooth.le.ScanCallback);
   }
 
   public abstract class ScanCallback {
     ctor public ScanCallback();
-    method public abstract void onAdvertisementFound(android.bluetooth.le.ScanResult);
-    method public abstract void onAdvertisementLost(android.bluetooth.le.ScanResult);
-    method public abstract void onAdvertisementUpdate(android.bluetooth.le.ScanResult);
-    method public abstract void onScanFailed(int);
+    method public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult>);
+    method public void onScanFailed(int);
+    method public void onScanResult(int, android.bluetooth.le.ScanResult);
     field public static final int SCAN_FAILED_ALREADY_STARTED = 1; // 0x1
     field public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; // 0x2
-    field public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4; // 0x4
-    field public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
-    field public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3; // 0x3
+    field public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4; // 0x4
+    field public static final int SCAN_FAILED_INTERNAL_ERROR = 3; // 0x3
   }
 
   public final class ScanFilter implements android.os.Parcelable {
     method public int describeContents();
     method public java.lang.String getDeviceAddress();
-    method public java.lang.String getLocalName();
+    method public java.lang.String getDeviceName();
     method public byte[] getManufacturerData();
     method public byte[] getManufacturerDataMask();
     method public int getManufacturerId();
@@ -6505,10 +6508,10 @@
   public static final class ScanFilter.Builder {
     ctor public ScanFilter.Builder();
     method public android.bluetooth.le.ScanFilter build();
-    method public android.bluetooth.le.ScanFilter.Builder setMacAddress(java.lang.String);
+    method public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(java.lang.String);
+    method public android.bluetooth.le.ScanFilter.Builder setDeviceName(java.lang.String);
     method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[]);
     method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[], byte[]);
-    method public android.bluetooth.le.ScanFilter.Builder setName(java.lang.String);
     method public android.bluetooth.le.ScanFilter.Builder setRssiRange(int, int);
     method public android.bluetooth.le.ScanFilter.Builder setServiceData(byte[]);
     method public android.bluetooth.le.ScanFilter.Builder setServiceData(byte[], byte[]);
@@ -6518,7 +6521,7 @@
 
   public final class ScanRecord {
     method public int getAdvertiseFlags();
-    method public java.lang.String getLocalName();
+    method public java.lang.String getDeviceName();
     method public int getManufacturerId();
     method public byte[] getManufacturerSpecificData();
     method public byte[] getServiceData();
@@ -6541,13 +6544,13 @@
   public final class ScanSettings implements android.os.Parcelable {
     method public int describeContents();
     method public int getCallbackType();
-    method public long getReportDelayNanos();
+    method public long getReportDelaySeconds();
     method public int getScanMode();
     method public int getScanResultType();
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final int CALLBACK_TYPE_ON_FOUND = 1; // 0x1
-    field public static final int CALLBACK_TYPE_ON_LOST = 2; // 0x2
-    field public static final int CALLBACK_TYPE_ON_UPDATE = 0; // 0x0
+    field public static final int CALLBACK_TYPE_ALL_MATCHES = 1; // 0x1
+    field public static final int CALLBACK_TYPE_FIRST_MATCH = 2; // 0x2
+    field public static final int CALLBACK_TYPE_MATCH_LOST = 4; // 0x4
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int SCAN_MODE_BALANCED = 1; // 0x1
     field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
@@ -6559,7 +6562,7 @@
     ctor public ScanSettings.Builder();
     method public android.bluetooth.le.ScanSettings build();
     method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int);
-    method public android.bluetooth.le.ScanSettings.Builder setReportDelayNanos(long);
+    method public android.bluetooth.le.ScanSettings.Builder setReportDelaySeconds(long);
     method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
   }
 
@@ -7679,6 +7682,7 @@
     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
@@ -13324,7 +13328,6 @@
     method public void setBackDisposition(int);
     method public void setCandidatesView(android.view.View);
     method public void setCandidatesViewShown(boolean);
-    method public void setCursorAnchorMonitorMode(int);
     method public void setExtractView(android.view.View);
     method public void setExtractViewShown(boolean);
     method public void setInputView(android.view.View);
@@ -13336,8 +13339,6 @@
     field public static final int BACK_DISPOSITION_DEFAULT = 0; // 0x0
     field public static final int BACK_DISPOSITION_WILL_DISMISS = 2; // 0x2
     field public static final int BACK_DISPOSITION_WILL_NOT_DISMISS = 1; // 0x1
-    field public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 1; // 0x1
-    field public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0; // 0x0
   }
 
   public class InputMethodService.InputMethodImpl extends android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl {
@@ -14204,11 +14205,14 @@
 
   public abstract class Image implements java.lang.AutoCloseable {
     method public abstract void close();
+    method public android.graphics.Rect getCropRect();
     method public abstract int getFormat();
     method public abstract int getHeight();
     method public abstract android.media.Image.Plane[] getPlanes();
     method public abstract long getTimestamp();
     method public abstract int getWidth();
+    method public void setCropRect(android.graphics.Rect);
+    field protected android.graphics.Rect mCropRect;
   }
 
   public static abstract class Image.Plane {
@@ -14283,15 +14287,22 @@
     method public final int dequeueOutputBuffer(android.media.MediaCodec.BufferInfo, long);
     method public final void flush();
     method public android.media.MediaCodecInfo getCodecInfo();
-    method public java.nio.ByteBuffer[] getInputBuffers();
+    method public java.nio.ByteBuffer getInputBuffer(int);
+    method public deprecated java.nio.ByteBuffer[] getInputBuffers();
+    method public final android.media.MediaFormat getInputFormat();
+    method public android.media.Image getInputImage(int);
     method public final java.lang.String getName();
-    method public java.nio.ByteBuffer[] getOutputBuffers();
+    method public java.nio.ByteBuffer getOutputBuffer(int);
+    method public deprecated java.nio.ByteBuffer[] getOutputBuffers();
     method public final android.media.MediaFormat getOutputFormat();
+    method public final android.media.MediaFormat getOutputFormat(int);
+    method public android.media.Image getOutputImage(int);
     method public final void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
     method public final void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
     method public final void release();
     method public final void releaseOutputBuffer(int, boolean);
     method public final void releaseOutputBuffer(int, long);
+    method public void setCallback(android.media.MediaCodec.Callback);
     method public final void setParameters(android.os.Bundle);
     method public final void setVideoScalingMode(int);
     method public final void signalEndOfInputStream();
@@ -14299,11 +14310,12 @@
     method public final void stop();
     field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2
     field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4
-    field public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
+    field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
+    field public static final deprecated int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
     field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
     field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
-    field public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
+    field public static final deprecated int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
     field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe
     field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff
     field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
@@ -14322,6 +14334,14 @@
     field public int size;
   }
 
+  public static abstract class MediaCodec.Callback {
+    ctor public MediaCodec.Callback();
+    method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
+    method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
+    method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
+    method public abstract void onOutputFormatChanged(android.media.MediaCodec, android.media.MediaFormat);
+  }
+
   public static final class MediaCodec.CodecException extends java.lang.IllegalStateException {
     ctor public MediaCodec.CodecException(int, int, java.lang.String);
     method public int getErrorCode();
@@ -14809,6 +14829,7 @@
     method public int getAudioSessionId();
     method public int getCurrentPosition();
     method public int getDuration();
+    method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
     method public android.media.MediaPlayer.TrackInfo[] getTrackInfo() throws java.lang.IllegalStateException;
     method public int getVideoHeight();
     method public int getVideoWidth();
@@ -14908,6 +14929,7 @@
     method public int getTrackType();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+    field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
     field public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; // 0x3
     field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
     field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
@@ -16149,6 +16171,7 @@
     method public boolean onKeyUp(int, android.view.KeyEvent);
     method public abstract void onRelease();
     method public boolean onSelectTrack(android.media.tv.TvTrackInfo);
+    method public abstract void onSetCaptionEnabled(boolean);
     method public abstract void onSetStreamVolume(float);
     method public abstract boolean onSetSurface(android.view.Surface);
     method public boolean onTouchEvent(android.view.MotionEvent);
@@ -16198,6 +16221,7 @@
     method public boolean onUnhandledInputEvent(android.view.InputEvent);
     method public void reset();
     method public void selectTrack(android.media.tv.TvTrackInfo);
+    method public void setCaptionEnabled(boolean);
     method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
     method public void setStreamVolume(float);
     method public void setTvInputListener(android.media.tv.TvView.TvInputListener);
@@ -17331,6 +17355,23 @@
     enum_constant public static final android.net.wifi.SupplicantState UNINITIALIZED;
   }
 
+  public class WifiAdapter implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getName();
+    method public boolean is5GHzBandSupported();
+    method public boolean isDeviceToApRttSupported();
+    method public boolean isDeviceToDeviceRttSupported();
+    method public boolean isEnhancedPowerReportingSupported();
+    method public boolean isOffChannelTdlsSupported();
+    method public boolean isP2pSupported();
+    method public boolean isPasspointSupported();
+    method public boolean isPortableHotspotSupported();
+    method public boolean isPreferredNetworkOffloadSupported();
+    method public boolean isTdlsSupported();
+    method public boolean isWifiScannerSupported();
+    method public void writeToParcel(android.os.Parcel, int);
+  }
+
   public class WifiConfiguration implements android.os.Parcelable {
     ctor public WifiConfiguration();
     method public int describeContents();
@@ -17469,6 +17510,7 @@
     method public boolean disableNetwork(int);
     method public boolean disconnect();
     method public boolean enableNetwork(int, boolean);
+    method public java.util.List<android.net.wifi.WifiAdapter> getAdapters();
     method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
@@ -26518,6 +26560,7 @@
     method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request);
     method public void onCloseSystemDialogs();
     method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
+    method public void onCompleteVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
     method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets);
     method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
     method public void onCreate(android.os.Bundle);
@@ -26553,6 +26596,7 @@
     method public void sendAbortVoiceResult(android.os.Bundle);
     method public void sendCancelResult();
     method public void sendCommandResult(boolean, android.os.Bundle);
+    method public void sendCompleteVoiceResult(android.os.Bundle);
     method public void sendConfirmResult(boolean, android.os.Bundle);
   }
 
@@ -27546,7 +27590,7 @@
   public final class CallCapabilities {
     method public static java.lang.String toString(int);
     field public static final int ADD_CALL = 16; // 0x10
-    field public static final int ALL = 255; // 0xff
+    field public static final int ALL = 3327; // 0xcff
     field public static final int GENERIC_CONFERENCE = 128; // 0x80
     field public static final int HOLD = 1; // 0x1
     field public static final int MERGE_CALLS = 4; // 0x4
@@ -27556,21 +27600,16 @@
     field public static final int SUPPORTS_VT_REMOTE = 512; // 0x200
     field public static final int SUPPORT_HOLD = 2; // 0x2
     field public static final int SWAP_CALLS = 8; // 0x8
+    field public static final int VoLTE = 1024; // 0x400
+    field public static final int VoWIFI = 2048; // 0x800
   }
 
-  public final class CallFeatures {
-    field public static final int NONE = 0; // 0x0
-    field public static final int VoLTE = 1; // 0x1
-    field public static final int VoWIFI = 2; // 0x2
-  }
-
-  public final class CallNumberPresentation extends java.lang.Enum {
-    method public static android.telecomm.CallNumberPresentation valueOf(java.lang.String);
-    method public static final android.telecomm.CallNumberPresentation[] values();
-    enum_constant public static final android.telecomm.CallNumberPresentation ALLOWED;
-    enum_constant public static final android.telecomm.CallNumberPresentation PAYPHONE;
-    enum_constant public static final android.telecomm.CallNumberPresentation RESTRICTED;
-    enum_constant public static final android.telecomm.CallNumberPresentation UNKNOWN;
+  public 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 {
@@ -27653,9 +27692,11 @@
     method public final android.telecomm.CallAudioState getCallAudioState();
     method public final int getCallCapabilities();
     method public final android.telecomm.CallVideoProvider getCallVideoProvider();
+    method public final java.lang.String getCallerDisplayName();
+    method public final int getCallerDisplayNamePresentation();
     method public final java.util.List<android.telecomm.Connection> getChildConnections();
-    method public final int getFeatures();
     method public final android.net.Uri getHandle();
+    method public final int getHandlePresentation();
     method public final android.telecomm.Connection getParentConnection();
     method public final int getState();
     method public final android.telecomm.StatusHints getStatusHints();
@@ -27674,16 +27715,17 @@
     method protected void onSetAudioState(android.telecomm.CallAudioState);
     method protected void onSetState(int);
     method protected void onStopDtmfTone();
+    method protected void onSwapWithBackgroundCall();
     method protected void onUnhold();
     method public final void setActive();
     method public final void setAudioModeIsVoip(boolean);
     method public final void setCallCapabilities(int);
     method public final void setCallVideoProvider(android.telecomm.CallVideoProvider);
+    method public final void setCallerDisplayName(java.lang.String, int);
     method public final void setDestroyed();
     method public final void setDialing();
     method public final void setDisconnected(int, java.lang.String);
-    method public final void setFeatures(int);
-    method public final void setHandle(android.net.Uri);
+    method public final void setHandle(android.net.Uri, int);
     method public final void setOnHold();
     method public final void setParentConnection(android.telecomm.Connection);
     method public final void setPostDialWait(java.lang.String);
@@ -27704,13 +27746,13 @@
   }
 
   public final class ConnectionRequest implements android.os.Parcelable {
-    ctor public ConnectionRequest(java.lang.String, android.net.Uri, android.os.Bundle, int);
-    ctor public ConnectionRequest(android.telecomm.PhoneAccount, java.lang.String, android.net.Uri, android.os.Bundle, int);
+    ctor public ConnectionRequest(android.telecomm.PhoneAccount, java.lang.String, android.net.Uri, int, android.os.Bundle, int);
     method public int describeContents();
     method public android.telecomm.PhoneAccount getAccount();
     method public java.lang.String getCallId();
     method public android.os.Bundle getExtras();
     method public android.net.Uri getHandle();
+    method public int getHandlePresentation();
     method public int getVideoState();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
@@ -27757,6 +27799,7 @@
     method public void rejectCall(java.lang.String, boolean, java.lang.String);
     method public void setAudioRoute(int);
     method public void stopDtmfTone(java.lang.String);
+    method public void swapWithBackgroundCall(java.lang.String);
     method public void unholdCall(java.lang.String);
   }
 
@@ -27764,15 +27807,17 @@
     method public int describeContents();
     method public android.telecomm.PhoneAccount getAccount();
     method public android.telecomm.RemoteCallVideoProvider getCallVideoProvider() throws android.os.RemoteException;
+    method public java.lang.String getCallerDisplayName();
+    method public int getCallerDisplayNamePresentation();
     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 int getFeatures();
     method public android.telecomm.GatewayInfo getGatewayInfo();
     method public android.net.Uri getHandle();
+    method public int getHandlePresentation();
     method public java.lang.String getId();
     method public android.telecomm.CallState getState();
     method public android.telecomm.StatusHints getStatusHints();
@@ -27841,9 +27886,12 @@
     method public void disconnect();
     method public boolean getAudioModeIsVoip();
     method public int getCallCapabilities();
+    method public java.lang.String getCallerDisplayName();
+    method public int getCallerDisplayNamePresentation();
     method public int getDisconnectCause();
     method public java.lang.String getDisconnectMessage();
-    method public int getFeatures();
+    method public android.net.Uri getHandle();
+    method public int getHandlePresentation();
     method public int getState();
     method public android.telecomm.StatusHints getStatusHints();
     method public void hold();
@@ -27853,19 +27901,21 @@
     method public void removeListener(android.telecomm.RemoteConnection.Listener);
     method public void setAudioState(android.telecomm.CallAudioState);
     method public void stopDtmf();
+    method public void swapWithBackgroundCall();
     method public void unhold();
   }
 
   public static abstract interface RemoteConnection.Listener {
+    method public abstract void onAudioModeIsVoipChanged(android.telecomm.RemoteConnection, boolean);
     method public abstract void onCallCapabilitiesChanged(android.telecomm.RemoteConnection, int);
+    method public abstract void onCallerDisplayNameChanged(android.telecomm.RemoteConnection, java.lang.String, int);
     method public abstract void onDestroyed(android.telecomm.RemoteConnection);
     method public abstract void onDisconnected(android.telecomm.RemoteConnection, int, java.lang.String);
-    method public abstract void onFeaturesChanged(android.telecomm.RemoteConnection, int);
+    method public abstract void onHandleChanged(android.telecomm.RemoteConnection, android.net.Uri, int);
     method public abstract void onPostDialWait(android.telecomm.RemoteConnection, java.lang.String);
     method public abstract void onRequestingRingback(android.telecomm.RemoteConnection, boolean);
-    method public abstract void onSetAudioModeIsVoip(android.telecomm.RemoteConnection, boolean);
-    method public abstract void onSetStatusHints(android.telecomm.RemoteConnection, android.telecomm.StatusHints);
     method public abstract void onStateChanged(android.telecomm.RemoteConnection, int);
+    method public abstract void onStatusHintsChanged(android.telecomm.RemoteConnection, android.telecomm.StatusHints);
   }
 
   public abstract interface Response {
@@ -27879,9 +27929,10 @@
   }
 
   public final class StatusHints implements android.os.Parcelable {
-    ctor public StatusHints(android.content.ComponentName, java.lang.String, int);
+    ctor public StatusHints(android.content.ComponentName, java.lang.String, int, android.os.Bundle);
     method public int describeContents();
     method public android.content.ComponentName getComponentName();
+    method public android.os.Bundle getExtras();
     method public android.graphics.drawable.Drawable getIcon(android.content.Context);
     method public int getIconId();
     method public java.lang.String getLabel();
@@ -31288,10 +31339,17 @@
 
   public final class Range {
     ctor public Range(T, T);
+    method public T clamp(T);
+    method public boolean contains(T);
+    method public boolean contains(android.util.Range<T>);
     method public static android.util.Range<T> create(T, T);
+    method public android.util.Range<T> extend(android.util.Range<T>);
+    method public android.util.Range<T> extend(T, T);
+    method public android.util.Range<T> extend(T);
     method public T getLower();
     method public T getUpper();
-    method public boolean inRange(T);
+    method public android.util.Range<T> intersect(android.util.Range<T>);
+    method public android.util.Range<T> intersect(T, T);
   }
 
   public final class Rational extends java.lang.Number implements java.lang.Comparable {
@@ -31307,6 +31365,7 @@
     method public boolean isNaN();
     method public boolean isZero();
     method public long longValue();
+    method public static android.util.Rational parseRational(java.lang.String) throws java.lang.NumberFormatException;
     field public static final android.util.Rational NEGATIVE_INFINITY;
     field public static final android.util.Rational NaN;
     field public static final android.util.Rational POSITIVE_INFINITY;
@@ -31317,6 +31376,7 @@
     ctor public Size(int, int);
     method public int getHeight();
     method public int getWidth();
+    method public static android.util.Size parseSize(java.lang.String) throws java.lang.NumberFormatException;
   }
 
   public final class SizeF {
@@ -33612,6 +33672,7 @@
     method protected boolean getChildStaticTransformation(android.view.View, android.view.animation.Transformation);
     method public boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
     method public boolean getClipChildren();
+    method public boolean getClipToPadding();
     method public int getDescendantFocusability();
     method public android.view.View getFocusedChild();
     method public android.view.animation.LayoutAnimationController getLayoutAnimation();
@@ -34979,6 +35040,7 @@
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
     method public static final void removeComposingSpans(android.text.Spannable);
     method public boolean reportFullscreenMode(boolean);
+    method public int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest);
     method public boolean sendKeyEvent(android.view.KeyEvent);
     method public boolean setComposingRegion(int, int);
     method public static void setComposingSpans(android.text.Spannable);
@@ -35044,6 +35106,25 @@
     method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
   }
 
+  public final class CursorAnchorInfoRequest implements android.os.Parcelable {
+    ctor public CursorAnchorInfoRequest(int, int);
+    ctor public CursorAnchorInfoRequest(android.os.Parcel);
+    method public int describeContents();
+    method public int getRequestFlags();
+    method public int getRequestType();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE = 2; // 0x2
+    field public static final int FLAG_CURSOR_ANCHOR_INFO_MONITOR = 1; // 0x1
+    field public static final int FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES = 2; // 0x2
+    field public static final int FLAG_CURSOR_RECT_MONITOR = 1; // 0x1
+    field public static final int FLAG_CURSOR_RECT_WITH_VIEW_MATRIX = 4; // 0x4
+    field public static final int RESULT_NOT_HANDLED = 0; // 0x0
+    field public static final int RESULT_SCHEDULED = 1; // 0x1
+    field public static final int TYPE_CURSOR_ANCHOR_INFO = 1; // 0x1
+    field public static final int TYPE_CURSOR_RECT = 2; // 0x2
+  }
+
   public class EditorInfo implements android.text.InputType android.os.Parcelable {
     ctor public EditorInfo();
     method public int describeContents();
@@ -35141,6 +35222,7 @@
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
     method public abstract boolean reportFullscreenMode(boolean);
+    method public abstract int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest);
     method public abstract boolean sendKeyEvent(android.view.KeyEvent);
     method public abstract boolean setComposingRegion(int, int);
     method public abstract boolean setComposingText(java.lang.CharSequence, int);
@@ -35168,6 +35250,7 @@
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
     method public boolean reportFullscreenMode(boolean);
+    method public int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest);
     method public boolean sendKeyEvent(android.view.KeyEvent);
     method public boolean setComposingRegion(int, int);
     method public boolean setComposingText(java.lang.CharSequence, int);
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 92e9290..3d0eec4 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -27,10 +27,11 @@
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageManager;
+import android.content.pm.InstallSessionParams;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.PackageInstaller.CommitResultCallback;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
@@ -768,6 +769,31 @@
         }
     }
 
+    class LocalCommitResultCallback extends CommitResultCallback {
+        boolean finished;
+        boolean success;
+        String msg;
+
+        private void setResult(boolean success, String msg) {
+            synchronized (this) {
+                this.finished = true;
+                this.success = success;
+                this.msg = msg;
+                notifyAll();
+            }
+        }
+
+        @Override
+        public void onSuccess() {
+            setResult(true, null);
+        }
+
+        @Override
+        public void onFailure(String msg) {
+            setResult(false, msg);
+        }
+    }
+
     /**
      * Converts a failure code into a string by using reflection to find a matching constant
      * in PackageManager.
@@ -989,9 +1015,10 @@
     private void runInstallCreate() throws RemoteException {
         String installerPackageName = null;
 
-        final PackageInstallerParams params = new PackageInstallerParams();
+        final InstallSessionParams params = new InstallSessionParams();
         params.installFlags = PackageManager.INSTALL_ALL_USERS;
         params.fullInstall = true;
+        params.progressMax = -1;
 
         String opt;
         while ((opt = nextOption()) != null) {
@@ -1016,6 +1043,7 @@
                 params.fullInstall = false;
             } else if (opt.equals("-S")) {
                 params.deltaSize = Long.parseLong(nextOptionData());
+                params.progressMax = (int) params.deltaSize;
             } else {
                 throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -1067,7 +1095,8 @@
             out = session.openWrite(splitName, 0, sizeBytes);
 
             final int n = Streams.copy(in, out);
-            out.flush();
+            session.fsync(out);
+            session.addProgress(n);
 
             System.out.println("Success: streamed " + n + " bytes");
         } finally {
@@ -1084,21 +1113,21 @@
         try {
             session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
 
-            final LocalPackageInstallObserver observer = new LocalPackageInstallObserver();
-            session.install(observer);
+            final LocalCommitResultCallback callback = new LocalCommitResultCallback();
+            session.commit(callback);
 
-            synchronized (observer) {
-                while (!observer.finished) {
+            synchronized (callback) {
+                while (!callback.finished) {
                     try {
-                        observer.wait();
+                        callback.wait();
                     } catch (InterruptedException e) {
                     }
                 }
-                if (observer.result != PackageManager.INSTALL_SUCCEEDED) {
-                    throw new IllegalStateException(
-                            "Failure [" + installFailureToString(observer) + "]");
+                if (!callback.success) {
+                    throw new IllegalStateException("Failure [" + callback.msg + "]");
                 }
             }
+
             System.out.println("Success");
         } finally {
             IoUtils.closeQuietly(session);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3cf8f9a..e1d0b86 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -696,6 +696,12 @@
          */
         public TaskDescription taskDescription;
 
+        /**
+         * Task affiliation for grouping with other tasks.
+         * @hide
+         */
+        public int affiliatedTaskId;
+
         public RecentTaskInfo() {
         }
 
@@ -727,6 +733,7 @@
             dest.writeInt(userId);
             dest.writeLong(firstActiveTime);
             dest.writeLong(lastActiveTime);
+            dest.writeInt(affiliatedTaskId);
         }
 
         public void readFromParcel(Parcel source) {
@@ -741,6 +748,7 @@
             userId = source.readInt();
             firstActiveTime = source.readLong();
             lastActiveTime = source.readLong();
+            affiliatedTaskId = source.readInt();
         }
 
         public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index 941efbd..7117111 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -23,13 +23,14 @@
 public class PackageInstallObserver {
     private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
         @Override
-        public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
+        public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+                String msg) {
             PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
         }
     };
 
     /** {@hide} */
-    public IPackageInstallObserver2.Stub getBinder() {
+    public IPackageInstallObserver2 getBinder() {
         return mBinder;
     }
 
@@ -50,4 +51,9 @@
      */
     public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
     }
+
+    public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+            String msg) {
+        packageInstalled(basePackageName, extras, returnCode);
+    }
 }
diff --git a/core/java/android/app/PackageUninstallObserver.java b/core/java/android/app/PackageUninstallObserver.java
index 0a960a7..83fc380 100644
--- a/core/java/android/app/PackageUninstallObserver.java
+++ b/core/java/android/app/PackageUninstallObserver.java
@@ -28,7 +28,7 @@
     };
 
     /** {@hide} */
-    public IPackageDeleteObserver.Stub getBinder() {
+    public IPackageDeleteObserver getBinder() {
         return mBinder;
     }
 
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index abd042b..ac74ca1 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -16,11 +16,9 @@
 
 package android.app;
 
-import android.app.UiModeManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
-import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
@@ -30,6 +28,7 @@
 
 import com.android.internal.R;
 
+import static android.os.Build.VERSION_CODES.L;
 
 /**
  * A dialog that prompts the user for the time of day using a {@link TimePicker}.
@@ -108,18 +107,19 @@
 
         Context themeContext = getContext();
 
+        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+        if (targetSdkVersion < L) {
+            setIcon(0);
+            setTitle(R.string.time_picker_dialog_title);
+            setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
+        }
+
         LayoutInflater inflater =
                 (LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         View view = inflater.inflate(R.layout.time_picker_dialog, null);
         setView(view);
         mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
 
-        // Initialize state
-        UiModeManager uiModeManager =
-                (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
-        if (uiModeManager.getCurrentModeType() != Configuration.UI_MODE_TYPE_TELEVISION) {
-            mTimePicker.setLegacyMode(false /* will show new UI */);
-        }
         mTimePicker.setShowDoneButton(true);
         mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
             @Override
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 64e3484..4aec9e0 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -673,6 +673,7 @@
             canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
             canvas.drawBitmap(screenShot, 0, 0, null);
             canvas.setBitmap(null);
+            screenShot.recycle();
             screenShot = unrotatedScreenShot;
         }
 
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 0d947217..dcdfd78 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -81,6 +81,16 @@
                         request.clear();
                     }
                     break;
+                case MSG_COMPLETE_VOICE_RESULT:
+                    request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+                    if (DEBUG) Log.d(TAG, "onCompleteVoice: req="
+                            + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+                            + " result=" + args.arg1);
+                    if (request != null) {
+                        ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2);
+                        request.clear();
+                    }
+                    break;
                 case MSG_ABORT_VOICE_RESULT:
                     request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
                     if (DEBUG) Log.d(TAG, "onAbortVoice: req="
@@ -125,6 +135,12 @@
         }
 
         @Override
+        public void deliverCompleteVoiceResult(IVoiceInteractorRequest request, Bundle result) {
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
+                    MSG_COMPLETE_VOICE_RESULT, request, result));
+        }
+
+        @Override
         public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
             mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
                     MSG_ABORT_VOICE_RESULT, request, result));
@@ -147,9 +163,10 @@
     final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
 
     static final int MSG_CONFIRMATION_RESULT = 1;
-    static final int MSG_ABORT_VOICE_RESULT = 2;
-    static final int MSG_COMMAND_RESULT = 3;
-    static final int MSG_CANCEL_RESULT = 4;
+    static final int MSG_COMPLETE_VOICE_RESULT = 2;
+    static final int MSG_ABORT_VOICE_RESULT = 3;
+    static final int MSG_COMMAND_RESULT = 4;
+    static final int MSG_CANCEL_RESULT = 5;
 
     public static abstract class Request {
         IVoiceInteractorRequest mRequestInterface;
@@ -228,6 +245,36 @@
         }
     }
 
+    public static class CompleteVoiceRequest extends Request {
+        final CharSequence mMessage;
+        final Bundle mExtras;
+
+        /**
+         * Reports that the current interaction was successfully completed with voice, so the
+         * application can report the final status to the user. When the response comes back, the
+         * voice system has handled the request and is ready to switch; at that point the
+         * application can start a new non-voice activity or finish.  Be sure when starting the new
+         * activity to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+         * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+         * interaction task.
+         *
+         * @param message Optional message to tell user about the completion status of the task.
+         * @param extras Additional optional information.
+         */
+        public CompleteVoiceRequest(CharSequence message, Bundle extras) {
+            mMessage = message;
+            mExtras = extras;
+        }
+
+        public void onCompleteResult(Bundle result) {
+        }
+
+        IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+                IVoiceInteractorCallback callback) throws RemoteException {
+            return interactor.startCompleteVoice(packageName, callback, mMessage, mExtras);
+        }
+    }
+
     public static class AbortVoiceRequest extends Request {
         final CharSequence mMessage;
         final Bundle mExtras;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index be14504..97e3fc5 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -20,7 +20,9 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
 import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
 import android.content.Context;
 import android.os.Handler;
 import android.os.IBinder;
@@ -1734,7 +1736,10 @@
      *
      * @param callback the callback LE scan results are delivered
      * @return true, if the scan was started successfully
+     * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)}
+     *             instead.
      */
+    @Deprecated
     public boolean startLeScan(LeScanCallback callback) {
         return startLeScan(null, callback);
     }
@@ -1751,7 +1756,10 @@
      * @param serviceUuids Array of services to look for
      * @param callback the callback LE scan results are delivered
      * @return true, if the scan was started successfully
+     * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)}
+     *             instead.
      */
+    @Deprecated
     public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {
         if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);
 
@@ -1794,7 +1802,9 @@
      *
      * @param callback used to identify which scan to stop
      *        must be the same handle used to start the scan
+     * @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead.
      */
+    @Deprecated
     public void stopLeScan(LeScanCallback callback) {
         if (DBG) Log.d(TAG, "stopLeScan()");
         GattCallbackWrapper wrapper;
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 5ed10a6..0f0eee6 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -18,7 +18,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.AdvertisementData;
+import android.bluetooth.le.AdvertiseData;
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanSettings;
 import android.os.ParcelUuid;
@@ -40,8 +40,8 @@
     void stopScan(in int appIf, in boolean isServer);
     void flushPendingBatchResults(in int appIf, in boolean isServer);
     void startMultiAdvertising(in int appIf,
-                               in AdvertisementData advertiseData,
-                               in AdvertisementData scanResponse,
+                               in AdvertiseData advertiseData,
+                               in AdvertiseData scanResponse,
                                in AdvertiseSettings settings);
     void stopMultiAdvertising(in int appIf);
     void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java
index 1d26ef9..2afbadf4 100644
--- a/core/java/android/bluetooth/le/AdvertiseCallback.java
+++ b/core/java/android/bluetooth/le/AdvertiseCallback.java
@@ -17,64 +17,58 @@
 package android.bluetooth.le;
 
 /**
- * Callback of Bluetooth LE advertising, which is used to deliver advertising operation status.
+ * Bluetooth LE advertising callbacks, used to deliver advertising operation status.
  */
 public abstract class AdvertiseCallback {
 
     /**
-     * The operation is success.
-     *
+     * The requested operation was successful.
      * @hide
      */
-    public static final int SUCCESS = 0;
+    public static final int ADVERTISE_SUCCESS = 0;
+
     /**
-     * Fails to start advertising as the advertisement data contains services that are not added to
-     * the local bluetooth GATT server.
+     * Failed to start advertising as the advertisement data contains services that are not
+     * added to the local Bluetooth GATT server.
      */
     public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1;
+
     /**
-     * Fails to start advertising as system runs out of quota for advertisers.
+     * Failed to start advertising because no advertising instance is available.
      */
     public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
 
     /**
-     * Fails to start advertising as the advertising is already started.
+     * Failed to start advertising as the advertising is already started.
      */
     public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
+
     /**
-     * Fails to stop advertising as the advertising is not started.
+     * Operation failed due to an internal error.
      */
-    public static final int ADVERTISE_FAILED_NOT_STARTED = 4;
+    public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4;
 
     /**
-     * Operation fails due to bluetooth controller failure.
+     * This feature is not supported on this platform.
      */
-    public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5;
-
-    /**
-     * Operation fails due to GATT service failure.
-     * @hide
-     */
-    public static final int ADVERTISE_FAILED_GATT_SERVICE_FAILURE = 6;
-
-    /**
-     * Operation fails as this feature is not supported
-     */
-    public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 7;
+    public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5;
 
 
     /**
-     * Callback when advertising operation succeeds.
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertising} indicating
+     * that the advertising has been started successfully.
      *
      * @param settingsInEffect The actual settings used for advertising, which may be different from
-     *            what the app asks.
+     *            what has been requested.
      */
-    public abstract void onSuccess(AdvertiseSettings settingsInEffect);
+    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+    }
 
     /**
-     * Callback when advertising operation fails.
+     * Callback when advertising could not be started.
      *
-     * @param errorCode Error code for failures.
+     * @param errorCode Error code (see ADVERTISE_FAILED_* constants) for
      */
-    public abstract void onFailure(int errorCode);
+    public void onStartFailure(int errorCode) {
+    }
 }
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/bluetooth/le/AdvertiseData.aidl
similarity index 95%
rename from core/java/android/bluetooth/le/AdvertisementData.aidl
rename to core/java/android/bluetooth/le/AdvertiseData.aidl
index 3da1321..bcbf224 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/bluetooth/le/AdvertiseData.aidl
@@ -16,4 +16,4 @@
 
 package android.bluetooth.le;
 
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable AdvertiseData;
diff --git a/core/java/android/bluetooth/le/AdvertisementData.java b/core/java/android/bluetooth/le/AdvertiseData.java
similarity index 83%
rename from core/java/android/bluetooth/le/AdvertisementData.java
rename to core/java/android/bluetooth/le/AdvertiseData.java
index c587204..d0f52b2 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.java
+++ b/core/java/android/bluetooth/le/AdvertiseData.java
@@ -27,16 +27,16 @@
 import java.util.List;
 
 /**
- * Advertisement data packet for Bluetooth LE advertising. This represents the data to be
- * broadcasted in Bluetooth LE advertising as well as the scan response for active scan.
- * <p>
- * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to be
+ * Advertisement data packet container for Bluetooth LE advertising. This represents the data to be
+ * advertised as well as the scan response data for active scans.
+ *
+ * <p>Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be
  * advertised.
  *
  * @see BluetoothLeAdvertiser
  * @see ScanRecord
  */
-public final class AdvertisementData implements Parcelable {
+public final class AdvertiseData implements Parcelable {
 
     @Nullable
     private final List<ParcelUuid> mServiceUuids;
@@ -52,7 +52,7 @@
 
     private boolean mIncludeTxPowerLevel;
 
-    private AdvertisementData(List<ParcelUuid> serviceUuids,
+    private AdvertiseData(List<ParcelUuid> serviceUuids,
             ParcelUuid serviceDataUuid, byte[] serviceData,
             int manufacturerId,
             byte[] manufacturerSpecificData, boolean includeTxPowerLevel) {
@@ -65,8 +65,8 @@
     }
 
     /**
-     * Returns a list of service uuids within the advertisement that are used to identify the
-     * bluetooth GATT services.
+     * Returns a list of service UUIDs within the advertisement that are used to identify the
+     * Bluetooth GATT services.
      */
     public List<ParcelUuid> getServiceUuids() {
         return mServiceUuids;
@@ -89,15 +89,14 @@
     }
 
     /**
-     * Returns a 16 bit uuid of the service that the service data is associated with.
+     * Returns a 16-bit UUID of the service that the service data is associated with.
      */
     public ParcelUuid getServiceDataUuid() {
         return mServiceDataUuid;
     }
 
     /**
-     * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
-     * service data.
+     * Returns service data.
      */
     public byte[] getServiceData() {
         return mServiceData;
@@ -112,7 +111,7 @@
 
     @Override
     public String toString() {
-        return "AdvertisementData [mServiceUuids=" + mServiceUuids + ", mManufacturerId="
+        return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mManufacturerId="
                 + mManufacturerId + ", mManufacturerSpecificData="
                 + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
                 + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
@@ -156,15 +155,15 @@
         dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
     }
 
-    public static final Parcelable.Creator<AdvertisementData> CREATOR =
-            new Creator<AdvertisementData>() {
+    public static final Parcelable.Creator<AdvertiseData> CREATOR =
+            new Creator<AdvertiseData>() {
             @Override
-                public AdvertisementData[] newArray(int size) {
-                    return new AdvertisementData[size];
+                public AdvertiseData[] newArray(int size) {
+                    return new AdvertiseData[size];
                 }
 
             @Override
-                public AdvertisementData createFromParcel(Parcel in) {
+                public AdvertiseData createFromParcel(Parcel in) {
                     Builder builder = new Builder();
                     if (in.readInt() > 0) {
                         List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
@@ -194,7 +193,7 @@
             };
 
     /**
-     * Builder for {@link AdvertisementData}.
+     * Builder for {@link AdvertiseData}.
      */
     public static final class Builder {
         private static final int MAX_ADVERTISING_DATA_BYTES = 31;
@@ -215,11 +214,13 @@
         private byte[] mServiceData;
 
         /**
-         * Set the service uuids. Note the corresponding bluetooth Gatt services need to be already
-         * added on the device before start BLE advertising.
+         * Set the service UUIDs.
          *
-         * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or 128-bit
-         *            uuids.
+         * <p><b>Note:</b> The corresponding Bluetooth Gatt services need to already
+         * be added on the device (using {@link android.bluetooth.BluetoothGattServer#addService}) prior
+         * to advertising them.
+         *
+         * @param serviceUuids Service UUIDs to be advertised.
          * @throws IllegalArgumentException If the {@code serviceUuids} are null.
          */
         public Builder setServiceUuids(List<ParcelUuid> serviceUuids) {
@@ -233,9 +234,8 @@
         /**
          * Add service data to advertisement.
          *
-         * @param serviceDataUuid A 16 bit uuid of the service data
-         * @param serviceData Service data - the first two bytes of the service data are the service
-         *            data uuid.
+         * @param serviceDataUuid 16-bit UUID of the service the data is associated with
+         * @param serviceData Service data
          * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
          *             empty.
          */
@@ -250,13 +250,14 @@
         }
 
         /**
-         * Set manufacturer id and data. See <a
-         * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned
-         * manufacturer identifies</a> for the existing company identifiers.
+         * Set manufacturer specific data.
          *
-         * @param manufacturerId Manufacturer id assigned by Bluetooth SIG.
-         * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of the
-         *            manufacturer specific data are the manufacturer id.
+         * <p>Please refer to the Bluetooth Assigned Numbers document provided by the
+         * <a href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing
+         * company identifiers.
+         *
+         * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG.
+         * @param manufacturerSpecificData Manufacturer specific data
          * @throws IllegalArgumentException If the {@code manufacturerId} is negative or
          *             {@code manufacturerSpecificData} is null.
          */
@@ -282,16 +283,16 @@
         }
 
         /**
-         * Build the {@link AdvertisementData}.
+         * Build the {@link AdvertiseData}.
          *
          * @throws IllegalArgumentException If the data size is larger than 31 bytes.
          */
-        public AdvertisementData build() {
+        public AdvertiseData build() {
             if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) {
                 throw new IllegalArgumentException(
                         "advertisement data size is larger than 31 bytes");
             }
-            return new AdvertisementData(mServiceUuids,
+            return new AdvertiseData(mServiceUuids,
                     mServiceDataUuid,
                     mServiceData, mManufacturerId, mManufacturerSpecificData,
                     mIncludeTxPowerLevel);
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java
index 87d0346..02b4a5b 100644
--- a/core/java/android/bluetooth/le/AdvertiseSettings.java
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.java
@@ -21,7 +21,8 @@
 
 /**
  * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each
- * individual advertisement. Use {@link AdvertiseSettings.Builder} to create an instance.
+ * Bluetooth LE advertisement instance. Use {@link AdvertiseSettings.Builder} to create an
+ * instance of this class.
  */
 public final class AdvertiseSettings implements Parcelable {
     /**
@@ -29,68 +30,64 @@
      * advertising mode as it consumes the least power.
      */
     public static final int ADVERTISE_MODE_LOW_POWER = 0;
+
     /**
      * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising
      * frequency and power consumption.
      */
     public static final int ADVERTISE_MODE_BALANCED = 1;
+
     /**
      * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power
-     * consumption and should not be used for background continuous advertising.
+     * consumption and should not be used for continuous background advertising.
      */
     public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
 
     /**
-     * Advertise using the lowest transmission(tx) power level. An app can use low transmission
-     * power to restrict the visibility range of its advertising packet.
+     * Advertise using the lowest transmission (TX) power level. Low transmission power can be used
+     * to restrict the visibility range of advertising packets.
      */
     public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
+
     /**
-     * Advertise using low tx power level.
+     * Advertise using low TX power level.
      */
     public static final int ADVERTISE_TX_POWER_LOW = 1;
+
     /**
-     * Advertise using medium tx power level.
+     * Advertise using medium TX power level.
      */
     public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
+
     /**
-     * Advertise using high tx power level. This is corresponding to largest visibility range of the
+     * Advertise using high TX power level. This corresponds to largest visibility range of the
      * advertising packet.
      */
     public static final int ADVERTISE_TX_POWER_HIGH = 3;
 
     /**
-     * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.1
-     * vol6, part B, section 4.4.2 - Advertising state.
+     * The maximimum limited advertisement duration as specified by the Bluetooth SIG
      */
-    public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0;
-    /**
-     * Scannable undirected advertise type, as defined in same spec mentioned above. This event type
-     * allows a scanner to send a scan request asking additional information about the advertiser.
-     */
-    public static final int ADVERTISE_TYPE_SCANNABLE = 1;
-    /**
-     * Connectable undirected advertising type, as defined in same spec mentioned above. This event
-     * type allows a scanner to send scan request asking additional information about the
-     * advertiser. It also allows an initiator to send a connect request for connection.
-     */
-    public static final int ADVERTISE_TYPE_CONNECTABLE = 2;
+    private static final int LIMITED_ADVERTISING_MAX_DURATION = 180;
 
     private final int mAdvertiseMode;
     private final int mAdvertiseTxPowerLevel;
-    private final int mAdvertiseEventType;
+    private final int mAdvertiseTimeoutSeconds;
+    private final boolean mAdvertiseConnectable;
 
     private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
-            int advertiseEventType) {
+            boolean advertiseConnectable, int advertiseTimeout) {
         mAdvertiseMode = advertiseMode;
         mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
-        mAdvertiseEventType = advertiseEventType;
+        mAdvertiseConnectable = advertiseConnectable;
+        mAdvertiseTimeoutSeconds = advertiseTimeout;
     }
 
     private AdvertiseSettings(Parcel in) {
         mAdvertiseMode = in.readInt();
         mAdvertiseTxPowerLevel = in.readInt();
-        mAdvertiseEventType = in.readInt();
+        mAdvertiseConnectable = in.readInt() != 0 ? true : false;
+        mAdvertiseTimeoutSeconds = in.readInt();
     }
 
     /**
@@ -101,23 +98,32 @@
     }
 
     /**
-     * Returns the tx power level for advertising.
+     * Returns the TX power level for advertising.
      */
     public int getTxPowerLevel() {
         return mAdvertiseTxPowerLevel;
     }
 
     /**
-     * Returns the advertise event type.
+     * Returns whether the advertisement will indicate connectable.
      */
-    public int getType() {
-        return mAdvertiseEventType;
+    public boolean getIsConnectable() {
+        return mAdvertiseConnectable;
+    }
+
+    /**
+     * Returns the advertising time limit in seconds.
+     */
+    public int getTimeout() {
+        return mAdvertiseTimeoutSeconds;
     }
 
     @Override
     public String toString() {
-        return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel="
-                + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]";
+        return "Settings [mAdvertiseMode=" + mAdvertiseMode
+             + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel
+             + ", mAdvertiseConnectable=" + mAdvertiseConnectable
+             + ", mAdvertiseTimeoutSeconds=" + mAdvertiseTimeoutSeconds + "]";
     }
 
     @Override
@@ -129,7 +135,8 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mAdvertiseMode);
         dest.writeInt(mAdvertiseTxPowerLevel);
-        dest.writeInt(mAdvertiseEventType);
+        dest.writeInt(mAdvertiseConnectable ? 1 : 0);
+        dest.writeInt(mAdvertiseTimeoutSeconds);
     }
 
     public static final Parcelable.Creator<AdvertiseSettings> CREATOR =
@@ -151,7 +158,8 @@
     public static final class Builder {
         private int mMode = ADVERTISE_MODE_LOW_POWER;
         private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
-        private int mType = ADVERTISE_TYPE_NON_CONNECTABLE;
+        private int mTimeoutSeconds = 0;
+        private boolean mConnectable = true;
 
         /**
          * Set advertise mode to control the advertising power and latency.
@@ -172,7 +180,7 @@
         }
 
         /**
-         * Set advertise tx power level to control the transmission power level for the advertising.
+         * Set advertise TX power level to control the transmission power level for the advertising.
          *
          * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of
          *            {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW},
@@ -191,20 +199,28 @@
         }
 
         /**
-         * Set advertise type to control the event type of advertising.
+         * Set whether the advertisement type should be connectable or non-connectable.
          *
-         * @param type Bluetooth LE Advertising type, can be either
-         *            {@link AdvertiseSettings#ADVERTISE_TYPE_NON_CONNECTABLE},
-         *            {@link AdvertiseSettings#ADVERTISE_TYPE_SCANNABLE} or
-         *            {@link AdvertiseSettings#ADVERTISE_TYPE_CONNECTABLE}.
-         * @throws IllegalArgumentException If the {@code type} is invalid.
+         * @param isConnectable Controls whether the advertisment type will be connectable (true)
+         *                    or non-connectable (false).
          */
-        public Builder setType(int type) {
-            if (type < ADVERTISE_TYPE_NON_CONNECTABLE
-                    || type > ADVERTISE_TYPE_CONNECTABLE) {
-                throw new IllegalArgumentException("unknown advertise type " + type);
+        public Builder setIsConnectable(boolean isConnectable) {
+            mConnectable = isConnectable;
+            return this;
+        }
+
+        /**
+         * Limit advertising to a given amount of time.
+         * @param timeoutSeconds Advertising time limit. May not exceed 180 seconds.
+         *                       A value of 0 will disable the time limit.
+         * @throws IllegalArgumentException If the provided timeout is over 180s.
+         */
+        public Builder setTimeout(int timeoutSeconds) {
+            if (timeoutSeconds < 0 || timeoutSeconds > LIMITED_ADVERTISING_MAX_DURATION) {
+                throw new IllegalArgumentException("timeoutSeconds invalid (must be 0-"
+                                    + LIMITED_ADVERTISING_MAX_DURATION + " seconds)");
             }
-            mType = type;
+            mTimeoutSeconds = timeoutSeconds;
             return this;
         }
 
@@ -212,7 +228,7 @@
          * Build the {@link AdvertiseSettings} object.
          */
         public AdvertiseSettings build() {
-            return new AdvertiseSettings(mMode, mTxPowerLevel, mType);
+            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutSeconds);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index d395d43..e232512 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -33,17 +33,17 @@
 import java.util.UUID;
 
 /**
- * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop
- * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by
- * {@link AdvertisementData}.
+ * This class provides a way to perform Bluetooth LE advertise operations, such as starting and
+ * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data
+ * represented by {@link AdvertiseData}.
  * <p>
  * To get an instance of {@link BluetoothLeAdvertiser}, call the
  * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
  * <p>
- * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
  * permission.
  *
- * @see AdvertisementData
+ * @see AdvertiseData
  */
 public final class BluetoothLeAdvertiser {
 
@@ -57,8 +57,6 @@
 
     /**
      * Use BluetoothAdapter.getLeAdvertiser() instead.
-     *
-     * @param bluetoothManager
      * @hide
      */
     public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
@@ -68,8 +66,8 @@
     }
 
     /**
-     * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
-     * operation succeeds. Returns immediately, the operation status are delivered through
+     * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be
+     * broadcasted. Returns immediately, the operation status is delivered through
      * {@code callback}.
      * <p>
      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
@@ -79,15 +77,15 @@
      * @param callback Callback for advertising status.
      */
     public void startAdvertising(AdvertiseSettings settings,
-            AdvertisementData advertiseData, final AdvertiseCallback callback) {
+            AdvertiseData advertiseData, final AdvertiseCallback callback) {
         startAdvertising(settings, advertiseData, null, callback);
     }
 
     /**
-     * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
-     * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends
-     * active scan request. Method returns immediately, the operation status are delivered through
-     * {@code callback}.
+     * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
+     * operation succeeds. The {@code scanResponse} is returned when a scanning device sends
+     * an active scan request. This method returns immediately, the operation status is
+     * delivered through {@code callback}.
      * <p>
      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
@@ -97,7 +95,7 @@
      * @param callback Callback for advertising status.
      */
     public void startAdvertising(AdvertiseSettings settings,
-            AdvertisementData advertiseData, AdvertisementData scanResponse,
+            AdvertiseData advertiseData, AdvertiseData scanResponse,
             final AdvertiseCallback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("callback cannot be null");
@@ -110,8 +108,8 @@
         try {
             gatt = mBluetoothManager.getBluetoothGatt();
         } catch (RemoteException e) {
-            Log.e(TAG, "failed to get bluetooth gatt - ", e);
-            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_CONTROLLER_FAILURE);
+            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
             return;
         }
         if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
@@ -127,7 +125,7 @@
                 mLeAdvertisers.put(callback, wrapper);
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "failed to stop advertising", e);
+            Log.e(TAG, "Failed to stop advertising", e);
         }
     }
 
@@ -137,31 +135,24 @@
      * <p>
      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
      *
-     * @param callback {@link AdvertiseCallback} for delivering stopping advertising status.
+     * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
      */
     public void stopAdvertising(final AdvertiseCallback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("callback cannot be null");
         }
         AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
-        if (wrapper == null) {
-            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED);
-            return;
-        }
+        if (wrapper == null) return;
+
         try {
             IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt();
-            if (gatt == null) {
-                postCallbackFailure(callback,
-                        AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE);
-            }
-            gatt.stopMultiAdvertising(wrapper.mLeHandle);
+            if (gatt != null) gatt.stopMultiAdvertising(wrapper.mLeHandle);
+
             if (wrapper.advertiseStopped()) {
                 mLeAdvertisers.remove(callback);
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "failed to stop advertising", e);
-            postCallbackFailure(callback,
-                    AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE);
+            Log.e(TAG, "Failed to stop advertising", e);
         }
     }
 
@@ -171,8 +162,8 @@
     private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
         private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
         private final AdvertiseCallback mAdvertiseCallback;
-        private final AdvertisementData mAdvertisement;
-        private final AdvertisementData mScanResponse;
+        private final AdvertiseData mAdvertisement;
+        private final AdvertiseData mScanResponse;
         private final AdvertiseSettings mSettings;
         private final IBluetoothGatt mBluetoothGatt;
 
@@ -183,7 +174,7 @@
         private boolean isAdvertising = false;
 
         public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
-                AdvertisementData advertiseData, AdvertisementData scanResponse,
+                AdvertiseData advertiseData, AdvertiseData scanResponse,
                 AdvertiseSettings settings,
                 IBluetoothGatt bluetoothGatt) {
             mAdvertiseCallback = advertiseCallback;
@@ -347,8 +338,12 @@
 
         @Override
         public void onMultiAdvertiseCallback(int status) {
+            // TODO: This logic needs to be re-visited to account
+            //       for whether the scan has actually been started
+            //       or not. Toggling the isAdvertising does not seem
+            //       correct.
             synchronized (this) {
-                if (status == 0) {
+                if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
                     isAdvertising = !isAdvertising;
                     if (!isAdvertising) {
                         try {
@@ -357,10 +352,11 @@
                         } catch (RemoteException e) {
                             Log.e(TAG, "remote exception when unregistering", e);
                         }
+                    } else {
+                        mAdvertiseCallback.onStartSuccess(null);
                     }
-                    mAdvertiseCallback.onSuccess(null);
                 } else {
-                    mAdvertiseCallback.onFailure(status);
+                    if (!isAdvertising) mAdvertiseCallback.onStartFailure(status);
                 }
                 notifyAll();
             }
@@ -398,7 +394,7 @@
         mHandler.post(new Runnable() {
                 @Override
             public void run() {
-                callback.onFailure(error);
+                callback.onStartFailure(error);
             }
         });
     }
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index d5a4728..7e87edc 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -36,14 +36,14 @@
 
 /**
  * This class provides methods to perform scan related operations for Bluetooth LE devices. An
- * application can scan for a particular type of BLE devices using {@link ScanFilter}. It can also
- * request different types of callbacks for delivering the result.
+ * application can scan for a particular type of Bluetotoh LE devices using {@link ScanFilter}.
+ * It can also request different types of callbacks for delivering the result.
  * <p>
  * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
  * {@link BluetoothLeScanner}.
  * <p>
- * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
+ * <b>Note:</b> Most of the scan methods here require
+ * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
  *
  * @see ScanFilter
  */
@@ -58,6 +58,8 @@
     private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
 
     /**
+     * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
+     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
      * @hide
      */
     public BluetoothLeScanner(IBluetoothManager bluetoothManager) {
@@ -68,13 +70,29 @@
     }
 
     /**
+     * Start Bluetooth LE scan with default parameters and no filters.
+     * The scan results will be delivered through {@code callback}.
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param callback Callback used to deliver scan results.
+     * @throws IllegalArgumentException If {@code callback} is null.
+     */
+    public void startScan(final ScanCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback is null");
+        }
+        this.startScan(null, new ScanSettings.Builder().build(), callback);
+    }
+
+    /**
      * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
      * <p>
      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
      *
      * @param filters {@link ScanFilter}s for finding exact BLE devices.
-     * @param settings Settings for ble scan.
-     * @param callback Callback when scan results are delivered.
+     * @param settings Settings for the scan.
+     * @param callback Callback used to deliver scan results.
      * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
      */
     public void startScan(List<ScanFilter> filters, ScanSettings settings,
@@ -94,7 +112,7 @@
                 gatt = null;
             }
             if (gatt == null) {
-                postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE);
+                postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
                 return;
             }
             if (!isSettingsConfigAllowedForScan(settings)) {
@@ -116,7 +134,7 @@
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "GATT service exception when starting scan", e);
-                postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE);
+                postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
             }
         }
     }
@@ -139,24 +157,12 @@
     }
 
     /**
-     * Returns available storage size for batch scan results. It's recommended not to use batch scan
-     * if available storage size is small (less than 1k bytes, for instance).
-     *
-     * @hide TODO: unhide when batching is supported in stack.
-     */
-    public int getAvailableBatchStorageSizeBytes() {
-        throw new UnsupportedOperationException("not impelemented");
-    }
-
-    /**
      * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth
      * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data
      * will be delivered through the {@code callback}.
      *
      * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
      *            used to start scan.
-     *
-     * @hide
      */
     public void flushPendingScanResults(ScanCallback callback) {
         if (callback == null) {
@@ -302,7 +308,7 @@
             handler.post(new Runnable() {
                     @Override
                 public void run() {
-                    mScanCallback.onAdvertisementUpdate(result);
+                    mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
                 }
             });
 
@@ -435,9 +441,9 @@
             ScanResult result = new ScanResult(device, advData, rssi,
                     scanNanos);
             if (onFound) {
-                mScanCallback.onAdvertisementFound(result);
+                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, result);
             } else {
-                mScanCallback.onAdvertisementLost(result);
+                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, result);
             }
         }
     }
@@ -452,17 +458,13 @@
     }
 
     private boolean isSettingsConfigAllowedForScan(ScanSettings settings) {
-        boolean ret = true;
-        int callbackType;
-
-        callbackType = settings.getCallbackType();
-        if (((callbackType == ScanSettings.CALLBACK_TYPE_ON_LOST) ||
-                (callbackType == ScanSettings.CALLBACK_TYPE_ON_FOUND) ||
-                (callbackType == ScanSettings.CALLBACK_TYPE_ON_UPDATE &&
-                settings.getReportDelayNanos() > 0) &&
-                (!mBluetoothAdapter.isOffloadedFilteringSupported()))) {
-            ret = false;
+        final int callbackType = settings.getCallbackType();
+        if ((  callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+           || (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+           && settings.getReportDelaySeconds() > 0))
+           && !mBluetoothAdapter.isOffloadedFilteringSupported()) {
+            return false;
         }
-        return ret;
+        return true;
     }
 }
diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java
index 593f7f8..b4c1e17 100644
--- a/core/java/android/bluetooth/le/ScanCallback.java
+++ b/core/java/android/bluetooth/le/ScanCallback.java
@@ -19,64 +19,53 @@
 import java.util.List;
 
 /**
- * Callback of Bluetooth LE scans. The results of the scans will be delivered through the callbacks.
+ * Bluetooth LE scan callbacks.
+ * Scan results are reported using these callbacks.
+ *
+ * {@see BluetoothLeScanner#startScan}
  */
 public abstract class ScanCallback {
-
     /**
      * Fails to start scan as BLE scan with the same settings is already started by the app.
      */
     public static final int SCAN_FAILED_ALREADY_STARTED = 1;
+
     /**
      * Fails to start scan as app cannot be registered.
      */
     public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2;
+
     /**
-     * Fails to start scan due to gatt service failure.
+     * Fails to start scan due an internal error
      */
-    public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3;
-    /**
-     * Fails to start scan due to controller failure.
-     */
-    public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4;
+    public static final int SCAN_FAILED_INTERNAL_ERROR = 3;
 
     /**
      * Fails to start power optimized scan as this feature is not supported.
      */
-    public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 5;
+    public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4;
 
     /**
-     * Callback when a BLE advertisement is found.
+     * Callback when a BLE advertisement has been found.
      *
+     * @param callbackType Determines if this callback was triggered because of first match,
+     *    a lost match indication or a regular scan result.
      * @param result A Bluetooth LE scan result.
      */
-    public abstract void onAdvertisementUpdate(ScanResult result);
-
-    /**
-     * Callback when the BLE advertisement is found for the first time.
-     *
-     * @param result The Bluetooth LE scan result when the onFound event is triggered.
-     */
-    public abstract void onAdvertisementFound(ScanResult result);
-
-    /**
-     * Callback when the BLE advertisement was lost. Note a device has to be "found" before it's
-     * lost.
-     *
-     * @param result The Bluetooth scan result that was last found.
-     */
-    public abstract void onAdvertisementLost(ScanResult result);
+    public void onScanResult(int callbackType, ScanResult result) {
+    }
 
     /**
      * Callback when batch results are delivered.
      *
      * @param results List of scan results that are previously scanned.
-     * @hide
      */
-    public abstract void onBatchScanResults(List<ScanResult> results);
+    public void onBatchScanResults(List<ScanResult> results) {
+    }
 
     /**
-     * Callback when scan failed.
+     * Callback when scan could not be started.
      */
-    public abstract void onScanFailed(int errorCode);
+    public void onScanFailed(int errorCode) {
+    }
 }
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index c2e316b..e8a91b8 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -45,10 +45,10 @@
 public final class ScanFilter implements Parcelable {
 
     @Nullable
-    private final String mLocalName;
+    private final String mDeviceName;
 
     @Nullable
-    private final String mMacAddress;
+    private final String mDeviceAddress;
 
     @Nullable
     private final ParcelUuid mServiceUuid;
@@ -69,14 +69,14 @@
     private final int mMinRssi;
     private final int mMaxRssi;
 
-    private ScanFilter(String name, String macAddress, ParcelUuid uuid,
+    private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
             ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask,
             int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
             int minRssi, int maxRssi) {
-        mLocalName = name;
+        mDeviceName = name;
         mServiceUuid = uuid;
         mServiceUuidMask = uuidMask;
-        mMacAddress = macAddress;
+        mDeviceAddress = deviceAddress;
         mServiceData = serviceData;
         mServiceDataMask = serviceDataMask;
         mManufacturerId = manufacturerId;
@@ -93,13 +93,13 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mLocalName == null ? 0 : 1);
-        if (mLocalName != null) {
-            dest.writeString(mLocalName);
+        dest.writeInt(mDeviceName == null ? 0 : 1);
+        if (mDeviceName != null) {
+            dest.writeString(mDeviceName);
         }
-        dest.writeInt(mMacAddress == null ? 0 : 1);
-        if (mMacAddress != null) {
-            dest.writeString(mMacAddress);
+        dest.writeInt(mDeviceAddress == null ? 0 : 1);
+        if (mDeviceAddress != null) {
+            dest.writeString(mDeviceAddress);
         }
         dest.writeInt(mServiceUuid == null ? 0 : 1);
         if (mServiceUuid != null) {
@@ -145,10 +145,10 @@
                 public ScanFilter createFromParcel(Parcel in) {
                     Builder builder = new Builder();
                     if (in.readInt() == 1) {
-                        builder.setName(in.readString());
+                        builder.setDeviceName(in.readString());
                     }
                     if (in.readInt() == 1) {
-                        builder.setMacAddress(in.readString());
+                        builder.setDeviceAddress(in.readString());
                     }
                     if (in.readInt() == 1) {
                         ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
@@ -196,11 +196,11 @@
             };
 
     /**
-     * Returns the filter set the local name field of Bluetooth advertisement data.
+     * Returns the filter set the device name field of Bluetooth advertisement data.
      */
     @Nullable
-    public String getLocalName() {
-        return mLocalName;
+    public String getDeviceName() {
+        return mDeviceName;
     }
 
     /**
@@ -218,7 +218,7 @@
 
     @Nullable
     public String getDeviceAddress() {
-        return mMacAddress;
+        return mDeviceAddress;
     }
 
     @Nullable
@@ -249,14 +249,14 @@
     }
 
     /**
-     * Returns minimum value of rssi for the scan filter. {@link Integer#MIN_VALUE} if not set.
+     * Returns minimum value of RSSI for the scan filter. {@link Integer#MIN_VALUE} if not set.
      */
     public int getMinRssi() {
         return mMinRssi;
     }
 
     /**
-     * Returns maximum value of the rssi for the scan filter. {@link Integer#MAX_VALUE} if not set.
+     * Returns maximum value of the RSSI for the scan filter. {@link Integer#MAX_VALUE} if not set.
      */
     public int getMaxRssi() {
         return mMaxRssi;
@@ -272,7 +272,7 @@
         }
         BluetoothDevice device = scanResult.getDevice();
         // Device match.
-        if (mMacAddress != null && (device == null || !mMacAddress.equals(device.getAddress()))) {
+        if (mDeviceAddress != null && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
             return false;
         }
 
@@ -286,13 +286,13 @@
 
         // Scan record is null but there exist filters on it.
         if (scanRecord == null
-                && (mLocalName != null || mServiceUuid != null || mManufacturerData != null
+                && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
                         || mServiceData != null)) {
             return false;
         }
 
         // Local name match.
-        if (mLocalName != null && !mLocalName.equals(scanRecord.getLocalName())) {
+        if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
             return false;
         }
 
@@ -367,7 +367,7 @@
 
     @Override
     public String toString() {
-        return "BluetoothLeScanFilter [mLocalName=" + mLocalName + ", mMacAddress=" + mMacAddress
+        return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" + mDeviceAddress
                 + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask + ", mServiceData="
                 + Arrays.toString(mServiceData) + ", mServiceDataMask="
                 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
@@ -378,7 +378,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLocalName, mMacAddress, mManufacturerId, mManufacturerData,
+        return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, mManufacturerData,
                 mManufacturerDataMask, mMaxRssi, mMinRssi, mServiceData, mServiceDataMask,
                 mServiceUuid, mServiceUuidMask);
     }
@@ -392,8 +392,8 @@
             return false;
         }
         ScanFilter other = (ScanFilter) obj;
-        return Objects.equals(mLocalName, other.mLocalName) &&
-                Objects.equals(mMacAddress, other.mMacAddress) &&
+        return Objects.equals(mDeviceName, other.mDeviceName) &&
+                Objects.equals(mDeviceAddress, other.mDeviceAddress) &&
                         mManufacturerId == other.mManufacturerId &&
                 Objects.deepEquals(mManufacturerData, other.mManufacturerData) &&
                 Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) &&
@@ -409,8 +409,8 @@
      */
     public static final class Builder {
 
-        private String mLocalName;
-        private String mMacAddress;
+        private String mDeviceName;
+        private String mDeviceAddress;
 
         private ParcelUuid mServiceUuid;
         private ParcelUuid mUuidMask;
@@ -426,26 +426,26 @@
         private int mMaxRssi = Integer.MAX_VALUE;
 
         /**
-         * Set filter on local name.
+         * Set filter on device name.
          */
-        public Builder setName(String localName) {
-            mLocalName = localName;
+        public Builder setDeviceName(String deviceName) {
+            mDeviceName = deviceName;
             return this;
         }
 
         /**
-         * Set filter on device mac address.
+         * Set filter on device address.
          *
-         * @param macAddress The device mac address for the filter. It needs to be in the format of
-         *            "01:02:03:AB:CD:EF". The mac address can be validated using
+         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in
+         *            the format of "01:02:03:AB:CD:EF". The device address can be validated using
          *            {@link BluetoothAdapter#checkBluetoothAddress}.
-         * @throws IllegalArgumentException If the {@code macAddress} is invalid.
+         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
          */
-        public Builder setMacAddress(String macAddress) {
-            if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) {
-                throw new IllegalArgumentException("invalid mac address " + macAddress);
+        public Builder setDeviceAddress(String deviceAddress) {
+            if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
+                throw new IllegalArgumentException("invalid device address " + deviceAddress);
             }
-            mMacAddress = macAddress;
+            mDeviceAddress = deviceAddress;
             return this;
         }
 
@@ -564,7 +564,7 @@
         }
 
         /**
-         * Set the desired rssi range for the filter. A scan result with rssi in the range of
+         * Set the desired RSSI range for the filter. A scan result with RSSI in the range of
          * [minRssi, maxRssi] will be consider as a match.
          */
         public Builder setRssiRange(int minRssi, int maxRssi) {
@@ -579,7 +579,7 @@
          * @throws IllegalArgumentException If the filter cannot be built.
          */
         public ScanFilter build() {
-            return new ScanFilter(mLocalName, mMacAddress,
+            return new ScanFilter(mDeviceName, mDeviceAddress,
                     mServiceUuid, mUuidMask,
                     mServiceData, mServiceDataMask,
                     mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi);
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index bd7304b..fc46e76 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -66,7 +66,7 @@
     private final int mTxPowerLevel;
 
     // Local name of the Bluetooth LE device.
-    private final String mLocalName;
+    private final String mDeviceName;
 
     /**
      * Returns the advertising flags indicating the discoverable mode and capability of the device.
@@ -77,7 +77,7 @@
     }
 
     /**
-     * Returns a list of service uuids within the advertisement that are used to identify the
+     * Returns a list of service UUIDs within the advertisement that are used to identify the
      * bluetooth gatt services.
      */
     public List<ParcelUuid> getServiceUuids() {
@@ -94,22 +94,21 @@
 
     /**
      * Returns the manufacturer specific data which is the content of manufacturer specific data
-     * field. The first 2 bytes of the data contain the company id.
+     * field.
      */
     public byte[] getManufacturerSpecificData() {
         return mManufacturerSpecificData;
     }
 
     /**
-     * Returns a 16 bit uuid of the service that the service data is associated with.
+     * Returns a 16-bit UUID of the service that the service data is associated with.
      */
     public ParcelUuid getServiceDataUuid() {
         return mServiceDataUuid;
     }
 
     /**
-     * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
-     * service data.
+     * Returns service data.
      */
     public byte[] getServiceData() {
         return mServiceData;
@@ -130,8 +129,8 @@
      * Returns the local name of the BLE device. The is a UTF-8 encoded string.
      */
     @Nullable
-    public String getLocalName() {
-        return mLocalName;
+    public String getDeviceName() {
+        return mDeviceName;
     }
 
     private ScanRecord(List<ParcelUuid> serviceUuids,
@@ -144,7 +143,7 @@
         mManufacturerSpecificData = manufacturerSpecificData;
         mServiceDataUuid = serviceDataUuid;
         mServiceData = serviceData;
-        mLocalName = localName;
+        mDeviceName = localName;
         mAdvertiseFlags = advertiseFlags;
         mTxPowerLevel = txPowerLevel;
     }
@@ -155,7 +154,7 @@
                 + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
                 + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
                 + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
-                + ", mTxPowerLevel=" + mTxPowerLevel + ", mLocalName=" + mLocalName + "]";
+                + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
     }
 
     /**
@@ -223,7 +222,7 @@
                         break;
                     case DATA_TYPE_SERVICE_DATA:
                         serviceData = extractBytes(scanRecord, currentPos, dataLength);
-                        // The first two bytes of the service data are service data uuid.
+                        // The first two bytes of the service data are service data UUID.
                         int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
                         byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
                                 serviceUuidLength);
@@ -256,7 +255,7 @@
         }
     }
 
-    // Parse service uuids.
+    // Parse service UUIDs.
     private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
             int uuidLength, List<ParcelUuid> serviceUuids) {
         while (dataLength > 0) {
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 7c0f9e1..2097702 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -16,54 +16,66 @@
 
 package android.bluetooth.le;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
- * Settings for Bluetooth LE scan.
+ * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan}
+ * to define the parameters for the scan.
  */
 public final class ScanSettings implements Parcelable {
     /**
-     * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the
-     * least power.
+     * Perform Bluetooth LE scan in low power mode.
+     * This is the default scan mode as it consumes the least power.
      */
     public static final int SCAN_MODE_LOW_POWER = 0;
+
     /**
      * Perform Bluetooth LE scan in balanced power mode.
+     * Scan results are returned at a rate that provides a good trade-off between scan
+     * frequency and power consumption.
      */
     public static final int SCAN_MODE_BALANCED = 1;
+
     /**
-     * Scan using highest duty cycle. It's recommended only using this mode when the application is
-     * running in foreground.
+     * Scan using highest duty cycle.
+     * It's recommended to only use this mode when the application is running in the foreground.
      */
     public static final int SCAN_MODE_LOW_LATENCY = 2;
 
     /**
-     * Callback each time when a bluetooth advertisement is found.
+     * Triggger a callback for every Bluetooth advertisement found that matches the
+     * filter criteria. If no filter is active, all advertisement packets are reported.
      */
-    public static final int CALLBACK_TYPE_ON_UPDATE = 0;
-    /**
-     * Callback when a bluetooth advertisement is found for the first time.
-     */
-    public static final int CALLBACK_TYPE_ON_FOUND = 1;
-    /**
-     * Callback when a bluetooth advertisement is found for the first time, then lost.
-     */
-    public static final int CALLBACK_TYPE_ON_LOST = 2;
+    public static final int CALLBACK_TYPE_ALL_MATCHES = 1;
 
     /**
-     * Full scan result which contains device mac address, rssi, advertising and scan response and
-     * scan timestamp.
+     * A result callback is only triggered for the first advertisement packet received that
+     * matches the filter criteria.
+     */
+    public static final int CALLBACK_TYPE_FIRST_MATCH = 2;
+
+    /**
+     * Receive a callback when advertisements are no longer received from a device that has been
+     * previously reported by a first match callback.
+     */
+    public static final int CALLBACK_TYPE_MATCH_LOST = 4;
+
+    /**
+     * Request full scan results which contain the device, rssi, advertising data, scan response
+     * as well as the scan timestamp.
      */
     public static final int SCAN_RESULT_TYPE_FULL = 0;
+
     /**
-     * Truncated scan result which contains device mac address, rssi and scan timestamp. Note it's
-     * possible for an app to get more scan results that it asks if there are multiple apps using
-     * this type. TODO: decide whether we could unhide this setting.
-     *
+     * Request abbreviated scan results which contain the device, rssi and scan timestamp.
+     * <p><b>Note:</b> It is possible for an application to get more scan results than
+     * it asked for, if there are multiple apps using this type.
      * @hide
      */
-    public static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
+    @SystemApi
+    public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1;
 
     // Bluetooth LE scan mode.
     private int mScanMode;
@@ -75,7 +87,7 @@
     private int mScanResultType;
 
     // Time of delay for reporting the scan result
-    private long mReportDelayNanos;
+    private long mReportDelaySeconds;
 
     public int getScanMode() {
         return mScanMode;
@@ -92,23 +104,23 @@
     /**
      * Returns report delay timestamp based on the device clock.
      */
-    public long getReportDelayNanos() {
-        return mReportDelayNanos;
+    public long getReportDelaySeconds() {
+        return mReportDelaySeconds;
     }
 
     private ScanSettings(int scanMode, int callbackType, int scanResultType,
-            long reportDelayNanos) {
+            long reportDelaySeconds) {
         mScanMode = scanMode;
         mCallbackType = callbackType;
         mScanResultType = scanResultType;
-        mReportDelayNanos = reportDelayNanos;
+        mReportDelaySeconds = reportDelaySeconds;
     }
 
     private ScanSettings(Parcel in) {
         mScanMode = in.readInt();
         mCallbackType = in.readInt();
         mScanResultType = in.readInt();
-        mReportDelayNanos = in.readLong();
+        mReportDelaySeconds = in.readLong();
     }
 
     @Override
@@ -116,7 +128,7 @@
         dest.writeInt(mScanMode);
         dest.writeInt(mCallbackType);
         dest.writeInt(mScanResultType);
-        dest.writeLong(mReportDelayNanos);
+        dest.writeLong(mReportDelaySeconds);
     }
 
     @Override
@@ -142,15 +154,14 @@
      */
     public static final class Builder {
         private int mScanMode = SCAN_MODE_LOW_POWER;
-        private int mCallbackType = CALLBACK_TYPE_ON_UPDATE;
+        private int mCallbackType = CALLBACK_TYPE_ALL_MATCHES;
         private int mScanResultType = SCAN_RESULT_TYPE_FULL;
-        private long mReportDelayNanos = 0;
+        private long mReportDelaySeconds = 0;
 
         /**
          * Set scan mode for Bluetooth LE scan.
          *
-         * @param scanMode The scan mode can be one of
-         *            {@link ScanSettings#SCAN_MODE_LOW_POWER},
+         * @param scanMode The scan mode can be one of {@link ScanSettings#SCAN_MODE_LOW_POWER},
          *            {@link ScanSettings#SCAN_MODE_BALANCED} or
          *            {@link ScanSettings#SCAN_MODE_LOW_LATENCY}.
          * @throws IllegalArgumentException If the {@code scanMode} is invalid.
@@ -166,13 +177,13 @@
         /**
          * Set callback type for Bluetooth LE scan.
          *
-         * @param callbackType The callback type for the scan. Can only be
-         *            {@link ScanSettings#CALLBACK_TYPE_ON_UPDATE}.
+         * @param callbackType The callback type flags for the scan.
          * @throws IllegalArgumentException If the {@code callbackType} is invalid.
          */
         public Builder setCallbackType(int callbackType) {
-            if (callbackType < CALLBACK_TYPE_ON_UPDATE
-                    || callbackType > CALLBACK_TYPE_ON_LOST) {
+            if (callbackType < CALLBACK_TYPE_ALL_MATCHES
+             || callbackType > (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
+             || (callbackType & CALLBACK_TYPE_ALL_MATCHES) != CALLBACK_TYPE_ALL_MATCHES) {
                 throw new IllegalArgumentException("invalid callback type - " + callbackType);
             }
             mCallbackType = callbackType;
@@ -184,14 +195,14 @@
          *
          * @param scanResultType Type for scan result, could be either
          *            {@link ScanSettings#SCAN_RESULT_TYPE_FULL} or
-         *            {@link ScanSettings#SCAN_RESULT_TYPE_TRUNCATED}.
+         *            {@link ScanSettings#SCAN_RESULT_TYPE_ABBREVIATED}.
          * @throws IllegalArgumentException If the {@code scanResultType} is invalid.
-         *
          * @hide
          */
+        @SystemApi
         public Builder setScanResultType(int scanResultType) {
             if (scanResultType < SCAN_RESULT_TYPE_FULL
-                    || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) {
+                    || scanResultType > SCAN_RESULT_TYPE_ABBREVIATED) {
                 throw new IllegalArgumentException(
                         "invalid scanResultType - " + scanResultType);
             }
@@ -201,9 +212,13 @@
 
         /**
          * Set report delay timestamp for Bluetooth LE scan.
+         * @param reportDelaySeconds Set to 0 to be notified of results immediately.
+         *                           Values &gt;0 causes the scan results to be queued
+         *                           up and delivered after the requested delay or when
+         *                           the internal buffers fill up.
          */
-        public Builder setReportDelayNanos(long reportDelayNanos) {
-            mReportDelayNanos = reportDelayNanos;
+        public Builder setReportDelaySeconds(long reportDelaySeconds) {
+            mReportDelaySeconds = reportDelaySeconds;
             return this;
         }
 
@@ -212,7 +227,7 @@
          */
         public ScanSettings build() {
             return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
-                    mReportDelayNanos);
+                    mReportDelaySeconds);
         }
     }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e24dc84..4153a02 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3810,6 +3810,18 @@
      */
     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
      * called -- no BroadcastReceiver components will be launched.
      */
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
index 7205ce7..824d730 100644
--- a/core/java/android/content/pm/IPackageInstallObserver2.aidl
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -40,5 +40,5 @@
      * </tr>
      * </table>
      */
-    void packageInstalled(String basePackageName, in Bundle extras, int returnCode);
+    void packageInstalled(String basePackageName, in Bundle extras, int returnCode, String msg);
 }
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 4d6ee64..32460c9 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -17,17 +17,22 @@
 package android.content.pm;
 
 import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallerObserver;
 import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
 import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
 interface IPackageInstaller {
-    int createSession(String installerPackageName, in PackageInstallerParams params, int userId);
+    int createSession(String installerPackageName, in InstallSessionParams params, int userId);
     IPackageInstallerSession openSession(int sessionId);
 
-    int[] getSessions(String installerPackageName, int userId);
+    List<InstallSessionInfo> getSessions(int userId);
 
-    void uninstall(String basePackageName, int flags, in IPackageDeleteObserver observer, int userId);
-    void uninstallSplit(String basePackageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
+    void registerObserver(IPackageInstallerObserver observer, int userId);
+    void unregisterObserver(IPackageInstallerObserver observer, int userId);
+
+    void uninstall(String packageName, int flags, in IPackageDeleteObserver observer, int userId);
+    void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
 }
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/content/pm/IPackageInstallerObserver.aidl
similarity index 66%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/content/pm/IPackageInstallerObserver.aidl
index 3da1321..85660e4 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/content/pm/IPackageInstallerObserver.aidl
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package android.bluetooth.le;
+package android.content.pm;
 
-parcelable AdvertisementData;
\ No newline at end of file
+import android.content.pm.InstallSessionInfo;
+
+/** {@hide} */
+oneway interface IPackageInstallerObserver {
+    void onSessionCreated(in InstallSessionInfo info);
+    void onSessionProgress(int sessionId, int progress);
+    void onSessionFinished(int sessionId, boolean success);
+}
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index f881acd..d6775d4 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,7 +21,8 @@
 
 /** {@hide} */
 interface IPackageInstallerSession {
-    void updateProgress(int progress);
+    void setClientProgress(int progress);
+    void addClientProgress(int progress);
 
     ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
 
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/content/pm/InstallSessionInfo.aidl
similarity index 90%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/content/pm/InstallSessionInfo.aidl
index 3da1321..3d21bbd 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/content/pm/InstallSessionInfo.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.bluetooth.le;
+package android.content.pm;
 
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable InstallSessionInfo;
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
new file mode 100644
index 0000000..45606d1
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** {@hide} */
+public class InstallSessionInfo implements Parcelable {
+    public int sessionId;
+    public String installerPackageName;
+    public int progress;
+
+    public boolean fullInstall;
+    public String packageName;
+    public Bitmap icon;
+    public CharSequence title;
+
+    /** {@hide} */
+    public InstallSessionInfo() {
+    }
+
+    /** {@hide} */
+    public InstallSessionInfo(Parcel source) {
+        sessionId = source.readInt();
+        installerPackageName = source.readString();
+        progress = source.readInt();
+
+        fullInstall = source.readInt() != 0;
+        packageName = source.readString();
+        icon = source.readParcelable(null);
+        title = source.readString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(sessionId);
+        dest.writeString(installerPackageName);
+        dest.writeInt(progress);
+
+        dest.writeInt(fullInstall ? 1 : 0);
+        dest.writeString(packageName);
+        dest.writeParcelable(icon, flags);
+        dest.writeString(title != null ? title.toString() : null);
+    }
+
+    public static final Parcelable.Creator<InstallSessionInfo>
+            CREATOR = new Parcelable.Creator<InstallSessionInfo>() {
+                @Override
+                public InstallSessionInfo createFromParcel(Parcel p) {
+                    return new InstallSessionInfo(p);
+                }
+
+                @Override
+                public InstallSessionInfo[] newArray(int size) {
+                    return new InstallSessionInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/content/pm/InstallSessionParams.aidl
similarity index 90%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/content/pm/InstallSessionParams.aidl
index 3da1321..81b7574 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/content/pm/InstallSessionParams.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.bluetooth.le;
+package android.content.pm;
 
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable InstallSessionParams;
diff --git a/core/java/android/content/pm/PackageInstallerParams.java b/core/java/android/content/pm/InstallSessionParams.java
similarity index 62%
rename from core/java/android/content/pm/PackageInstallerParams.java
rename to core/java/android/content/pm/InstallSessionParams.java
index 527edee..43e3314 100644
--- a/core/java/android/content/pm/PackageInstallerParams.java
+++ b/core/java/android/content/pm/InstallSessionParams.java
@@ -21,12 +21,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/**
- * Parameters that define an installation session.
- *
- * {@hide}
- */
-public class PackageInstallerParams implements Parcelable {
+import com.android.internal.util.IndentingPrintWriter;
+
+/** {@hide} */
+public class InstallSessionParams implements Parcelable {
 
     // TODO: extend to support remaining VerificationParams
 
@@ -41,11 +39,13 @@
     /** {@hide} */
     public long deltaSize = -1;
     /** {@hide} */
-    public String basePackageName;
+    public int progressMax = 100;
+    /** {@hide} */
+    public String packageName;
     /** {@hide} */
     public Bitmap icon;
     /** {@hide} */
-    public String title;
+    public CharSequence title;
     /** {@hide} */
     public Uri originatingUri;
     /** {@hide} */
@@ -53,20 +53,19 @@
     /** {@hide} */
     public String abiOverride;
 
-    public PackageInstallerParams() {
+    public InstallSessionParams() {
     }
 
     /** {@hide} */
-    public PackageInstallerParams(Parcel source) {
+    public InstallSessionParams(Parcel source) {
         fullInstall = source.readInt() != 0;
         installFlags = source.readInt();
         installLocation = source.readInt();
         signatures = (Signature[]) source.readParcelableArray(null);
         deltaSize = source.readLong();
-        basePackageName = source.readString();
-        if (source.readInt() != 0) {
-            icon = Bitmap.CREATOR.createFromParcel(source);
-        }
+        progressMax = source.readInt();
+        packageName = source.readString();
+        icon = source.readParcelable(null);
         title = source.readString();
         originatingUri = source.readParcelable(null);
         referrerUri = source.readParcelable(null);
@@ -93,8 +92,12 @@
         this.deltaSize = deltaSize;
     }
 
-    public void setBasePackageName(String basePackageName) {
-        this.basePackageName = basePackageName;
+    public void setProgressMax(int progressMax) {
+        this.progressMax = progressMax;
+    }
+
+    public void setPackageName(String packageName) {
+        this.packageName = packageName;
     }
 
     public void setIcon(Bitmap icon) {
@@ -102,7 +105,7 @@
     }
 
     public void setTitle(CharSequence title) {
-        this.title = (title != null) ? title.toString() : null;
+        this.title = title;
     }
 
     public void setOriginatingUri(Uri originatingUri) {
@@ -113,6 +116,23 @@
         this.referrerUri = referrerUri;
     }
 
+    /** {@hide} */
+    public void dump(IndentingPrintWriter pw) {
+        pw.printPair("fullInstall", fullInstall);
+        pw.printHexPair("installFlags", installFlags);
+        pw.printPair("installLocation", installLocation);
+        pw.printPair("signatures", (signatures != null));
+        pw.printPair("deltaSize", deltaSize);
+        pw.printPair("progressMax", progressMax);
+        pw.printPair("packageName", packageName);
+        pw.printPair("icon", (icon != null));
+        pw.printPair("title", title);
+        pw.printPair("originatingUri", originatingUri);
+        pw.printPair("referrerUri", referrerUri);
+        pw.printPair("abiOverride", abiOverride);
+        pw.println();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -125,29 +145,25 @@
         dest.writeInt(installLocation);
         dest.writeParcelableArray(signatures, flags);
         dest.writeLong(deltaSize);
-        dest.writeString(basePackageName);
-        if (icon != null) {
-            dest.writeInt(1);
-            icon.writeToParcel(dest, flags);
-        } else {
-            dest.writeInt(0);
-        }
-        dest.writeString(title);
+        dest.writeInt(progressMax);
+        dest.writeString(packageName);
+        dest.writeParcelable(icon, flags);
+        dest.writeString(title != null ? title.toString() : null);
         dest.writeParcelable(originatingUri, flags);
         dest.writeParcelable(referrerUri, flags);
         dest.writeString(abiOverride);
     }
 
-    public static final Parcelable.Creator<PackageInstallerParams>
-            CREATOR = new Parcelable.Creator<PackageInstallerParams>() {
+    public static final Parcelable.Creator<InstallSessionParams>
+            CREATOR = new Parcelable.Creator<InstallSessionParams>() {
                 @Override
-                public PackageInstallerParams createFromParcel(Parcel p) {
-                    return new PackageInstallerParams(p);
+                public InstallSessionParams createFromParcel(Parcel p) {
+                    return new InstallSessionParams(p);
                 }
 
                 @Override
-                public PackageInstallerParams[] newArray(int size) {
-                    return new PackageInstallerParams[size];
+                public InstallSessionParams[] newArray(int size) {
+                    return new InstallSessionParams[size];
                 }
             };
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index a60a2fe..401be06 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,12 +19,16 @@
 import android.app.PackageInstallObserver;
 import android.app.PackageUninstallObserver;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
 import android.os.FileBridge;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.util.ExceptionUtils;
 
 import java.io.Closeable;
+import java.io.IOException;
 import java.io.OutputStream;
+import java.util.List;
 
 /** {@hide} */
 public class PackageInstaller {
@@ -42,9 +46,9 @@
         mUserId = userId;
     }
 
-    public boolean isPackageAvailable(String basePackageName) {
+    public boolean isPackageAvailable(String packageName) {
         try {
-            final ApplicationInfo info = mPm.getApplicationInfo(basePackageName,
+            final ApplicationInfo info = mPm.getApplicationInfo(packageName,
                     PackageManager.GET_UNINSTALLED_PACKAGES);
             return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
         } catch (NameNotFoundException e) {
@@ -52,19 +56,22 @@
         }
     }
 
-    public void installAvailablePackage(String basePackageName, PackageInstallObserver observer) {
+    public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
         int returnCode;
         try {
-            returnCode = mPm.installExistingPackage(basePackageName);
+            returnCode = mPm.installExistingPackage(packageName);
         } catch (NameNotFoundException e) {
             returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
         }
-        observer.packageInstalled(basePackageName, null, returnCode);
+        observer.packageInstalled(packageName, null, returnCode);
     }
 
-    public int createSession(PackageInstallerParams params) {
+    public int createSession(InstallSessionParams params) throws IOException {
         try {
             return mInstaller.createSession(mInstallerPackageName, params, mUserId);
+        } catch (RuntimeException e) {
+            ExceptionUtils.maybeUnwrapIOException(e);
+            throw e;
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -78,26 +85,73 @@
         }
     }
 
-    public int[] getSessions() {
+    public List<InstallSessionInfo> getSessions() {
+        // TODO: filter based on caller
+        // TODO: let launcher app see all active sessions
         try {
-            return mInstaller.getSessions(mInstallerPackageName, mUserId);
+            return mInstaller.getSessions(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
-    public void uninstall(String basePackageName, PackageUninstallObserver observer) {
+    public void uninstall(String packageName, UninstallResultCallback callback) {
         try {
-            mInstaller.uninstall(basePackageName, 0, observer.getBinder(), mUserId);
+            mInstaller.uninstall(packageName, 0,
+                    new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
-    public void uninstall(String basePackageName, String splitName,
-            PackageUninstallObserver observer) {
+    public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
         try {
-            mInstaller.uninstallSplit(basePackageName, splitName, 0, observer.getBinder(), mUserId);
+            mInstaller.uninstallSplit(packageName, splitName, 0,
+                    new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public static abstract class SessionObserver {
+        private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() {
+            @Override
+            public void onSessionCreated(InstallSessionInfo info) {
+                SessionObserver.this.onCreated(info);
+            }
+
+            @Override
+            public void onSessionProgress(int sessionId, int progress) {
+                SessionObserver.this.onProgress(sessionId, progress);
+            }
+
+            @Override
+            public void onSessionFinished(int sessionId, boolean success) {
+                SessionObserver.this.onFinished(sessionId, success);
+            }
+        };
+
+        /** {@hide} */
+        public IPackageInstallerObserver getBinder() {
+            return mBinder;
+        }
+
+        public abstract void onCreated(InstallSessionInfo info);
+        public abstract void onProgress(int sessionId, int progress);
+        public abstract void onFinished(int sessionId, boolean success);
+    }
+
+    public void registerSessionObserver(SessionObserver observer) {
+        try {
+            mInstaller.registerObserver(observer.getBinder(), mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public void unregisterSessionObserver(SessionObserver observer) {
+        try {
+            mInstaller.unregisterObserver(observer.getBinder(), mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -123,9 +177,18 @@
             mSession = session;
         }
 
-        public void updateProgress(int progress) {
+        public void setProgress(int progress) {
             try {
-                mSession.updateProgress(progress);
+                mSession.setClientProgress(progress);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+
+        /** {@hide} */
+        public void addProgress(int progress) {
+            try {
+                mSession.addClientProgress(progress);
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
@@ -137,19 +200,31 @@
          * {@link OutputStream#flush()} to ensure bytes have been written to
          * disk.
          */
-        public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) {
+        public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes)
+                throws IOException {
             try {
                 final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName,
                         offsetBytes, lengthBytes);
                 return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor());
+            } catch (RuntimeException e) {
+                ExceptionUtils.maybeUnwrapIOException(e);
+                throw e;
             } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
+                throw new IOException(e);
             }
         }
 
-        public void install(PackageInstallObserver observer) {
+        public void fsync(OutputStream out) throws IOException {
+            if (out instanceof FileBridge.FileBridgeOutputStream) {
+                ((FileBridge.FileBridgeOutputStream) out).fsync();
+            } else {
+                throw new IllegalArgumentException("Unrecognized stream");
+            }
+        }
+
+        public void commit(CommitResultCallback callback) {
             try {
-                mSession.install(observer.getBinder());
+                mSession.install(new CommitResultCallbackDelegate(callback).getBinder());
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
@@ -168,4 +243,135 @@
             }
         }
     }
+
+    public static abstract class UninstallResultCallback {
+        public abstract void onSuccess();
+        public abstract void onFailure(String msg);
+    }
+
+    private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
+        private final UninstallResultCallback target;
+
+        public UninstallResultCallbackDelegate(UninstallResultCallback target) {
+            this.target = target;
+        }
+
+        @Override
+        public void onUninstallFinished(String basePackageName, int returnCode) {
+            final String msg = null;
+
+            switch (returnCode) {
+                case PackageManager.DELETE_SUCCEEDED: target.onSuccess(); break;
+                case PackageManager.DELETE_FAILED_INTERNAL_ERROR: target.onFailure("DELETE_FAILED_INTERNAL_ERROR: " + msg); break;
+                case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: target.onFailure("DELETE_FAILED_DEVICE_POLICY_MANAGER: " + msg); break;
+                case PackageManager.DELETE_FAILED_USER_RESTRICTED: target.onFailure("DELETE_FAILED_USER_RESTRICTED: " + msg); break;
+                case PackageManager.DELETE_FAILED_OWNER_BLOCKED: target.onFailure("DELETE_FAILED_OWNER_BLOCKED: " + msg); break;
+                default: target.onFailure(msg); break;
+            }
+        }
+    }
+
+    public static abstract class CommitResultCallback {
+        public abstract void onSuccess();
+        public abstract void onFailure(String msg);
+
+        /**
+         * One or more of the APKs included in the session was invalid. For
+         * example, they might be malformed, corrupt, incorrectly signed,
+         * mismatched, etc. The installer may want to try downloading and
+         * installing again.
+         */
+        public void onFailureInvalid(String msg) {
+            onFailure(msg);
+        }
+
+        /**
+         * 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.
+         */
+        public void onFailureConflict(String msg, String packageName) {
+            onFailure(msg);
+        }
+
+        /**
+         * This install session failed due to storage issues. For example,
+         * the device may be running low on space, or the required external
+         * media may be unavailable. The user may be able to help free space
+         * or insert the correct media.
+         */
+        public void onFailureStorage(String msg) {
+            onFailure(msg);
+        }
+
+        /**
+         * This install session is fundamentally incompatible with this
+         * device. For example, the package may require a hardware feature
+         * that doesn't exist, it may be missing native code for the device
+         * ABI, or it requires a newer SDK version, etc. This install would
+         * never succeed.
+         */
+        public void onFailureIncompatible(String msg) {
+            onFailure(msg);
+        }
+    }
+
+    private static class CommitResultCallbackDelegate extends PackageInstallObserver {
+        private final CommitResultCallback target;
+
+        public CommitResultCallbackDelegate(CommitResultCallback target) {
+            this.target = target;
+        }
+
+        @Override
+        public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+                String msg) {
+            final String otherPackage = null;
+
+            switch (returnCode) {
+                case PackageManager.INSTALL_SUCCEEDED: target.onSuccess(); break;
+                case PackageManager.INSTALL_FAILED_ALREADY_EXISTS: target.onFailureConflict("INSTALL_FAILED_ALREADY_EXISTS: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_INVALID_APK: target.onFailureInvalid("INSTALL_FAILED_INVALID_APK: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INVALID_URI: target.onFailureInvalid("INSTALL_FAILED_INVALID_URI: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE: target.onFailureStorage("INSTALL_FAILED_INSUFFICIENT_STORAGE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PACKAGE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_NO_SHARED_USER: target.onFailureConflict("INSTALL_FAILED_NO_SHARED_USER: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_UPDATE_INCOMPATIBLE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY: target.onFailureIncompatible("INSTALL_FAILED_MISSING_SHARED_LIBRARY: " + msg); break;
+                case PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE: target.onFailureConflict("INSTALL_FAILED_REPLACE_COULDNT_DELETE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_DEXOPT: target.onFailureInvalid("INSTALL_FAILED_DEXOPT: " + msg); break;
+                case PackageManager.INSTALL_FAILED_OLDER_SDK: target.onFailureIncompatible("INSTALL_FAILED_OLDER_SDK: " + msg); break;
+                case PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER: target.onFailureConflict("INSTALL_FAILED_CONFLICTING_PROVIDER: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_NEWER_SDK: target.onFailureIncompatible("INSTALL_FAILED_NEWER_SDK: " + msg); break;
+                case PackageManager.INSTALL_FAILED_TEST_ONLY: target.onFailureInvalid("INSTALL_FAILED_TEST_ONLY: " + msg); break;
+                case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: target.onFailureIncompatible("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_MISSING_FEATURE: target.onFailureIncompatible("INSTALL_FAILED_MISSING_FEATURE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_CONTAINER_ERROR: target.onFailureStorage("INSTALL_FAILED_CONTAINER_ERROR: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION: target.onFailureStorage("INSTALL_FAILED_INVALID_INSTALL_LOCATION: " + msg); break;
+                case PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE: target.onFailureStorage("INSTALL_FAILED_MEDIA_UNAVAILABLE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT: target.onFailure("INSTALL_FAILED_VERIFICATION_TIMEOUT: " + msg); break;
+                case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE: target.onFailure("INSTALL_FAILED_VERIFICATION_FAILURE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED: target.onFailureInvalid("INSTALL_FAILED_PACKAGE_CHANGED: " + msg); break;
+                case PackageManager.INSTALL_FAILED_UID_CHANGED: target.onFailureInvalid("INSTALL_FAILED_UID_CHANGED: " + msg); break;
+                case PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE: target.onFailureInvalid("INSTALL_FAILED_VERSION_DOWNGRADE: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_NOT_APK: target.onFailureInvalid("INSTALL_PARSE_FAILED_NOT_APK: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_MANIFEST: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: target.onFailureInvalid("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_NO_CERTIFICATES: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: target.onFailureInvalid("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_EMPTY: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INTERNAL_ERROR: target.onFailure("INSTALL_FAILED_INTERNAL_ERROR: " + msg); break;
+                case PackageManager.INSTALL_FAILED_USER_RESTRICTED: target.onFailureIncompatible("INSTALL_FAILED_USER_RESTRICTED: " + msg); break;
+                case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PERMISSION: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS: target.onFailureInvalid("INSTALL_FAILED_NO_MATCHING_ABIS: " + msg); break;
+                default: target.onFailure(msg); break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 625005b..03d4701 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -17,9 +17,9 @@
 package android.content.pm;
 
 import android.annotation.IntDef;
-import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,13 +29,12 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.util.AndroidException;
-import android.util.DisplayMetrics;
 
 import java.io.File;
 import java.lang.annotation.Retention;
@@ -750,7 +749,7 @@
      * permission that is already defined by some existing package.
      *
      * <p>The package name of the app which has already defined the permission is passed to
-     * a {@link IPackageInstallObserver2}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
+     * a {@link PackageInstallObserver}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
      * string extra; and the name of the permission being redefined is passed in the
      * {@link #EXTRA_EXISTING_PERMISSION} string extra.
      * @hide
@@ -841,8 +840,9 @@
     public static final int DELETE_FAILED_USER_RESTRICTED = -3;
 
     /**
-     * Deletion failed return code: this is returned from the PackageInstaller
-     * activity if it failed to delete a package because the a profile
+     * Deletion failed return code: this is passed to the
+     * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
+     * failed to delete the package because a profile
      * or device owner has marked the package as uninstallable.
      *
      * @hide
@@ -1454,6 +1454,14 @@
     public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: This device supports HDMI-CEC.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
+
+    /**
      * Action to external storage service to clean out removed apps.
      * @hide
      */
@@ -1543,7 +1551,7 @@
             = "android.content.pm.extra.PERMISSION_LIST";
 
     /**
-     * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+     * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
      * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}.  This extra names the package which provides
      * the existing definition for the permission.
      * @hide
@@ -1552,7 +1560,7 @@
             = "android.content.pm.extra.FAILURE_EXISTING_PACKAGE";
 
     /**
-     * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+     * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
      * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}.  This extra names the permission that is
      * being redundantly defined by the package being installed.
      * @hide
@@ -2940,26 +2948,29 @@
     }
 
     /**
-     * @hide
-     *
-     * Install a package. Since this may take a little while, the result will
-     * be posted back to the given observer.  An installation will fail if the calling context
-     * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
-     * package named in the package file's manifest is already installed, or if there's no space
-     * available on the device.
-     *
-     * @param packageURI The location of the package file to install.  This can be a 'file:' or a
-     * 'content:' URI.
-     * @param observer An observer callback to get notified when the package installation is
-     * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
-     * called when that happens.  This parameter must not be null.
+     * @hide Install a package. Since this may take a little while, the result
+     *       will be posted back to the given observer. An installation will
+     *       fail if the calling context lacks the
+     *       {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if
+     *       the package named in the package file's manifest is already
+     *       installed, or if there's no space available on the device.
+     * @param packageURI The location of the package file to install. This can
+     *            be a 'file:' or a 'content:' URI.
+     * @param observer An observer callback to get notified when the package
+     *            installation is complete.
+     *            {@link IPackageInstallObserver#packageInstalled(String, int)}
+     *            will be called when that happens. This parameter must not be
+     *            null.
      * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
-     * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
-     * @param installerPackageName Optional package name of the application that is performing the
-     * installation. This identifies which market the package came from.
-     * @deprecated Use {@link #installPackage(Uri, IPackageInstallObserver2, int, String)}
-     * instead.  This method will continue to be supported but the older observer interface
-     * will not get additional failure details.
+     *            {@link #INSTALL_REPLACE_EXISTING},
+     *            {@link #INSTALL_ALLOW_TEST}.
+     * @param installerPackageName Optional package name of the application that
+     *            is performing the installation. This identifies which market
+     *            the package came from.
+     * @deprecated Use {@link #installPackage(Uri, PackageInstallObserver, int,
+     *             String)} instead. This method will continue to be supported
+     *             but the older observer interface will not get additional
+     *             failure details.
      */
     // @SystemApi
     public abstract void installPackage(
@@ -2976,9 +2987,11 @@
      * @param observer An observer callback to get notified when the package
      *            installation is complete.
      *            {@link IPackageInstallObserver#packageInstalled(String, int)}
-     *            will be called when that happens. This parameter must not be null.
+     *            will be called when that happens. This parameter must not be
+     *            null.
      * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
-     *            {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+     *            {@link #INSTALL_REPLACE_EXISTING},
+     *            {@link #INSTALL_ALLOW_TEST}.
      * @param installerPackageName Optional package name of the application that
      *            is performing the installation. This identifies which market
      *            the package came from.
@@ -2991,10 +3004,11 @@
      *            these parameters describing the encryption and authentication
      *            used. May be {@code null}.
      * @hide
-     * @deprecated Use {@link #installPackageWithVerification(Uri, IPackageInstallObserver2,
-     * int, String, Uri, ManifestDigest, ContainerEncryptionParams)} instead.  This method will
-     * continue to be supported but the older observer interface will not get additional failure
-     * details.
+     * @deprecated Use {@link #installPackageWithVerification(Uri,
+     *             PackageInstallObserver, int, String, Uri, ManifestDigest,
+     *             ContainerEncryptionParams)} instead. This method will
+     *             continue to be supported but the older observer interface
+     *             will not get additional failure details.
      */
     // @SystemApi
     public abstract void installPackageWithVerification(Uri packageURI,
@@ -3012,9 +3026,11 @@
      * @param observer An observer callback to get notified when the package
      *            installation is complete.
      *            {@link IPackageInstallObserver#packageInstalled(String, int)}
-     *            will be called when that happens. This parameter must not be null.
+     *            will be called when that happens. This parameter must not be
+     *            null.
      * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
-     *            {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+     *            {@link #INSTALL_REPLACE_EXISTING},
+     *            {@link #INSTALL_ALLOW_TEST}.
      * @param installerPackageName Optional package name of the application that
      *            is performing the installation. This identifies which market
      *            the package came from.
@@ -3023,12 +3039,12 @@
      * @param encryptionParams if the package to be installed is encrypted,
      *            these parameters describing the encryption and authentication
      *            used. May be {@code null}.
-     *
      * @hide
      * @deprecated Use {@link #installPackageWithVerificationAndEncryption(Uri,
-     * IPackageInstallObserver2, int, String, VerificationParams,
-     * ContainerEncryptionParams)} instead.  This method will continue to be
-     * supported but the older observer interface will not get additional failure details.
+     *             PackageInstallObserver, int, String, VerificationParams,
+     *             ContainerEncryptionParams)} instead. This method will
+     *             continue to be supported but the older observer interface
+     *             will not get additional failure details.
      */
     @Deprecated
     public abstract void installPackageWithVerificationAndEncryption(Uri packageURI,
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0b6a926..d92a90d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -73,11 +73,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.jar.StrictJarFile;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index d505365..519bbb6 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -84,10 +84,11 @@
      * TODO: Remove these constants and strip out any code that previously relied on them
      * being set to true.
      */
-    static final boolean LIE_ABOUT_AE_STATE = true;
-    static final boolean LIE_ABOUT_AE_MAX_REGIONS = true;
+    static final boolean LIE_ABOUT_AE_STATE = false;
+    static final boolean LIE_ABOUT_AE_MAX_REGIONS = false;
     static final boolean LIE_ABOUT_AF = true;
     static final boolean LIE_ABOUT_AF_MAX_REGIONS = true;
+    static final boolean LIE_ABOUT_AWB_STATE = false;
     static final boolean LIE_ABOUT_AWB = true;
 
     /**
@@ -201,7 +202,7 @@
         /*
          * info.supportedHardwareLevel
          */
-        m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
+        m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
 
         /*
          * scaler.availableStream*, scaler.available*Durations, sensor.info.maxFrameDuration
@@ -330,16 +331,17 @@
             List<String> flashModes = p.getSupportedFlashModes();
 
             String[] flashModeStrings = new String[] {
+                    Camera.Parameters.FLASH_MODE_OFF,
                     Camera.Parameters.FLASH_MODE_AUTO,
                     Camera.Parameters.FLASH_MODE_ON,
                     Camera.Parameters.FLASH_MODE_RED_EYE,
                     // Map these manually
                     Camera.Parameters.FLASH_MODE_TORCH,
-                    Camera.Parameters.FLASH_MODE_OFF,
             };
             int[] flashModeInts = new int[] {
                     CONTROL_AE_MODE_ON,
                     CONTROL_AE_MODE_ON_AUTO_FLASH,
+                    CONTROL_AE_MODE_ON_ALWAYS_FLASH,
                     CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
             };
             int[] aeAvail = ArrayUtils.convertStringListToIntArray(
@@ -355,6 +357,25 @@
             // Note that AE_MODE_OFF is never available.
             m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
         }
+
+        /*
+         * control.aeCompensationRanges
+         */
+        {
+            int min = p.getMinExposureCompensation();
+            int max = p.getMaxExposureCompensation();
+
+            m.set(CONTROL_AE_COMPENSATION_RANGE, Range.create(min, max));
+        }
+
+        /*
+         * control.aeCompensationStep
+         */
+        {
+            float step = p.getExposureCompensationStep();
+
+            m.set(CONTROL_AE_COMPENSATION_STEP, ParamsUtils.createRational(step));
+        }
     }
 
     private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
index 17dda08..4a9afa6 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 import static com.android.internal.util.Preconditions.*;
 import static android.hardware.camera2.CaptureRequest.*;
@@ -41,12 +42,6 @@
     private static final String TAG = "LegacyRequestMapper";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    /** The default normalized camera area spans the entire size of the preview viewport */
-    private static final Camera.Area CAMERA_AREA_DEFAULT =
-            new Camera.Area(
-                    new Rect(/*left*/-1000, /*top*/-1000, /*right*/1000, /*bottom*/1000),
-                    /*weight*/1);
-
     /**
      * Set the legacy parameters using the {@link LegacyRequest legacy request}.
      *
@@ -61,40 +56,20 @@
         Size previewSize = legacyRequest.previewSize;
         Camera.Parameters params = legacyRequest.parameters;
 
+        Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+
         /*
          * scaler.cropRegion
          */
+        ParameterUtils.ZoomData zoomData;
         {
-            Rect activeArraySize = characteristics.get(
-                    CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-            Rect activeArraySizeOnly = new Rect(
-                    /*left*/0, /*top*/0,
-                    activeArraySize.width(), activeArraySize.height());
+            zoomData = ParameterUtils.convertScalerCropRegion(activeArray,
+                    request.get(SCALER_CROP_REGION),
+                    previewSize,
+                    params);
 
-            Rect userCropRegion = request.get(SCALER_CROP_REGION);
-
-            if (userCropRegion == null) {
-                userCropRegion = activeArraySizeOnly;
-            }
-
-            if (VERBOSE) {
-                Log.v(TAG, "convertRequestToMetadata - user crop region was " + userCropRegion);
-            }
-
-            Rect reportedCropRegion = new Rect();
-            Rect previewCropRegion = new Rect();
-            int zoomIndex = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly,
-                    previewSize, userCropRegion,
-                    /*out*/reportedCropRegion, /*out*/previewCropRegion);
-
-            if (VERBOSE) {
-                Log.v(TAG, "convertRequestToMetadata - zoom calculated to: " +
-                        "zoomIndex = " + zoomIndex +
-                        ", reported crop region = " + reportedCropRegion +
-                        ", preview crop region = " + previewCropRegion);
-            }
             if (params.isZoomSupported()) {
-                params.setZoom(zoomIndex);
+                params.setZoom(zoomData.zoomIndex);
             } else if (VERBOSE) {
                 Log.v(TAG, "convertRequestToMetadata - zoom is not supported");
             }
@@ -126,49 +101,29 @@
         }
 
         /*
-         * control.aeRegions
-         * -- ORDER OF EXECUTION MATTERS:
-         * -- This must be done after the crop region (zoom) was already set in the parameters
+         * control.aeRegions, afRegions
          */
         {
-            MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS);
-            int maxNumMeteringAreas = params.getMaxNumMeteringAreas();
-            if (aeRegions !=  null && maxNumMeteringAreas > 0) {
-                // Add all non-zero weight regions to the list
-                List<MeteringRectangle> meteringRectangleList = new ArrayList<>();
-                for (MeteringRectangle rect : aeRegions) {
-                    if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) {
-                        meteringRectangleList.add(rect);
-                    }
-                }
-
-                // Ignore any regions beyond our maximum supported count
-                int countMeteringAreas =
-                        Math.min(maxNumMeteringAreas, meteringRectangleList.size());
-                List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas);
-                Rect activeArray = characteristics.get(
-                        CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-
-                for (int i = 0; i < countMeteringAreas; ++i) {
-                    MeteringRectangle rect = meteringRectangleList.get(i);
-
-                    Camera.Area area = convertMeteringRectangleToLegacy(activeArray, rect);
-                    meteringAreaList.add(area);
-                }
+            // aeRegions
+            {
+                MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS);
+                int maxNumMeteringAreas = params.getMaxNumMeteringAreas();
+                List<Camera.Area> meteringAreaList = convertMeteringRegionsToLegacy(
+                        activeArray, zoomData, aeRegions, maxNumMeteringAreas,
+                        /*regionName*/"AE");
 
                 params.setMeteringAreas(meteringAreaList);
+            }
 
-                if (maxNumMeteringAreas < meteringRectangleList.size()) {
-                    Log.w(TAG,
-                            "convertRequestToMetadata - Too many requested AE regions, "
-                                    + "ignoring all beyond the first " + maxNumMeteringAreas);
-                }
-            } else {
-                if (maxNumMeteringAreas > 0) {
-                    params.setMeteringAreas(Arrays.asList(CAMERA_AREA_DEFAULT));
-                } else {
-                    params.setMeteringAreas(null);
-                }
+            // afRegions
+            {
+                MeteringRectangle[] afRegions = request.get(CONTROL_AF_REGIONS);
+                int maxNumFocusAreas = params.getMaxNumFocusAreas();
+                List<Camera.Area> focusAreaList = convertMeteringRegionsToLegacy(
+                        activeArray, zoomData, afRegions, maxNumFocusAreas,
+                        /*regionName*/"AF");
+
+                params.setFocusAreas(focusAreaList);
             }
         }
 
@@ -199,13 +154,102 @@
          * control
          */
 
+        // control.aeExposureCompensation
+        {
+            Range<Integer> compensationRange =
+                    characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
+            int compensation = getOrDefault(request,
+                    CONTROL_AE_EXPOSURE_COMPENSATION,
+                    /*defaultValue*/0);
+
+            if (!compensationRange.contains(compensation)) {
+                Log.w(TAG,
+                        "convertRequestMetadata - control.aeExposureCompensation " +
+                        "is out of range, ignoring value");
+                compensation = 0;
+            }
+
+            params.setExposureCompensation(compensation);
+        }
+
+        // control.aeLock
+        {
+            Boolean aeLock = getIfSupported(request, CONTROL_AE_LOCK, /*defaultValue*/false,
+                    params.isAutoExposureLockSupported(),
+                    /*allowedValue*/false);
+
+            if (aeLock != null) {
+                params.setAutoExposureLock(aeLock);
+            }
+
+            if (VERBOSE) {
+                Log.v(TAG, "convertRequestToMetadata - control.aeLock set to " + aeLock);
+            }
+
+            // TODO: Don't add control.aeLock to availableRequestKeys if it's not supported
+        }
+
         // control.aeMode, flash.mode
         mapAeAndFlashMode(request, /*out*/params);
 
         // control.awbLock
-        Boolean awbLock = request.get(CONTROL_AWB_LOCK);
-        params.setAutoWhiteBalanceLock(awbLock == null ? false : awbLock);
+        {
+            Boolean awbLock = getIfSupported(request, CONTROL_AWB_LOCK, /*defaultValue*/false,
+                    params.isAutoWhiteBalanceLockSupported(),
+                    /*allowedValue*/false);
 
+            if (awbLock != null) {
+                params.setAutoWhiteBalanceLock(awbLock);
+            }
+
+         // TODO: Don't add control.awbLock to availableRequestKeys if it's not supported
+        }
+    }
+
+    private static List<Camera.Area> convertMeteringRegionsToLegacy(
+            Rect activeArray, ParameterUtils.ZoomData zoomData,
+            MeteringRectangle[] meteringRegions, int maxNumMeteringAreas, String regionName) {
+        if (meteringRegions == null || maxNumMeteringAreas <= 0) {
+            if (maxNumMeteringAreas > 0) {
+                return Arrays.asList(ParameterUtils.CAMERA_AREA_DEFAULT);
+            } else {
+                return null;
+            }
+        }
+
+        // Add all non-zero weight regions to the list
+        List<MeteringRectangle> meteringRectangleList = new ArrayList<>();
+        for (MeteringRectangle rect : meteringRegions) {
+            if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) {
+                meteringRectangleList.add(rect);
+            }
+        }
+
+        // Ignore any regions beyond our maximum supported count
+        int countMeteringAreas =
+                Math.min(maxNumMeteringAreas, meteringRectangleList.size());
+        List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas);
+
+        for (int i = 0; i < countMeteringAreas; ++i) {
+            MeteringRectangle rect = meteringRectangleList.get(i);
+
+            ParameterUtils.MeteringData meteringData =
+                    ParameterUtils.convertMeteringRectangleToLegacy(activeArray, rect, zoomData);
+            meteringAreaList.add(meteringData.meteringArea);
+        }
+
+        if (maxNumMeteringAreas < meteringRectangleList.size()) {
+            Log.w(TAG,
+                    "convertMeteringRegionsToLegacy - Too many requested " + regionName +
+                            " regions, ignoring all beyond the first " + maxNumMeteringAreas);
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, "convertMeteringRegionsToLegacy - " + regionName + " areas = "
+                    + ParameterUtils.stringFromAreaList(meteringAreaList));
+        }
+
+        return meteringAreaList;
     }
 
     private static void mapAeAndFlashMode(CaptureRequest r, /*out*/Parameters p) {
@@ -214,34 +258,70 @@
 
         List<String> supportedFlashModes = p.getSupportedFlashModes();
 
+        String flashModeSetting = null;
+
+        // Flash is OFF by default, on cameras that support flash
+        if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_OFF)) {
+            flashModeSetting = Parameters.FLASH_MODE_OFF;
+        }
+
         /*
          * Map all of the control.aeMode* enums, but ignore AE_MODE_OFF since we never support it
          */
 
         // Ignore flash.mode controls unless aeMode == ON
         if (aeMode == CONTROL_AE_MODE_ON) {
-            // Flash is OFF by default
-            p.setFlashMode(Parameters.FLASH_MODE_OFF);
-
-            if (flashMode == FLASH_MODE_TORCH &&
-                    ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_TORCH)) {
-                p.setFlashMode(Parameters.FLASH_MODE_TORCH);
-            } else if (flashMode == FLASH_MODE_SINGLE &&
-                    ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
-                p.setFlashMode(Parameters.FLASH_MODE_ON);
+            if (flashMode == FLASH_MODE_TORCH) {
+                    if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_TORCH)) {
+                        flashModeSetting = Parameters.FLASH_MODE_TORCH;
+                    } else {
+                        Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == TORCH;" +
+                                "camera does not support it");
+                    }
+            } else if (flashMode == FLASH_MODE_SINGLE) {
+                if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
+                    flashModeSetting = Parameters.FLASH_MODE_ON;
+                } else {
+                    Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == SINGLE;" +
+                            "camera does not support it");
+                }
+            } else {
+                // Use the default FLASH_MODE_OFF
             }
-        } else if (aeMode == CONTROL_AE_MODE_ON_ALWAYS_FLASH &&
-                ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
-            p.setFlashMode(Parameters.FLASH_MODE_ON);
-        } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH &&
-                ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_AUTO)) {
-            p.setFlashMode(Parameters.FLASH_MODE_AUTO);
-        } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE &&
-                ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_RED_EYE)) {
-            p.setFlashMode(Parameters.FLASH_MODE_RED_EYE);
+        } else if (aeMode == CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
+                if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
+                    flashModeSetting = Parameters.FLASH_MODE_ON;
+                } else {
+                    Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_ALWAYS_FLASH;" +
+                            "camera does not support it");
+                }
+        } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH) {
+            if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_AUTO)) {
+                flashModeSetting = Parameters.FLASH_MODE_AUTO;
+            } else {
+                Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH;" +
+                        "camera does not support it");
+            }
+        } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) {
+                if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_RED_EYE)) {
+                    flashModeSetting = Parameters.FLASH_MODE_RED_EYE;
+                } else {
+                    Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH_REDEYE;"
+                            + "camera does not support it");
+                }
         } else {
             // Default to aeMode == ON, flash = OFF
-            p.setFlashMode(Parameters.FLASH_MODE_OFF);
+        }
+
+        if (flashModeSetting != null) {
+            p.setFlashMode(flashModeSetting);
+        }
+
+        if (VERBOSE) {
+                Log.v(TAG,
+                        "mapAeAndFlashMode - set flash.mode (api1) to " + flashModeSetting
+                        + ", requested (api2) " + flashMode
+                        + ", supported (api1) " + ListUtils.listToString(supportedFlashModes));
         }
     }
 
@@ -275,21 +355,7 @@
         return legacyFps;
     }
 
-    private static Camera.Area convertMeteringRectangleToLegacy(
-            Rect activeArray, MeteringRectangle meteringRect) {
-        // TODO: use matrix transform magic here
-
-        Rect rect = new Rect();
-
-        // TODO: Take the cropRegion (zooming) into account here
-
-        // TODO: crop to be within [-1000, 1000] range for both X and Y if the values end up too big
-        //return new Camera.Area(rect, meteringRect.getMeteringWeight());
-
-        Log.w(TAG, "convertMeteringRectangleToLegacy - TODO: support metering rects");
-        return CAMERA_AREA_DEFAULT;
-    }
-
+    /** Return the value set by the key, or the {@code defaultValue} if no value was set. */
     private static <T> T getOrDefault(CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue) {
         checkNotNull(r, "r must not be null");
         checkNotNull(key, "key must not be null");
@@ -302,4 +368,29 @@
             return value;
         }
     }
+
+    /**
+     * Return {@code null} if the value is not supported, otherwise return the retrieved key's
+     * value from the request (or the default value if it wasn't set).
+     *
+     * <p>If the fetched value in the request is equivalent to {@code allowedValue},
+     * then omit the warning (e.g. turning off AF lock on a camera
+     * that always has the AF lock turned off is a silent no-op), but still return {@code null}.</p>
+     *
+     * <p>Logs a warning to logcat if the key is not supported by api1 camera device.</p.
+     */
+    private static <T> T getIfSupported(
+            CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue, boolean isSupported,
+            T allowedValue) {
+        T val = getOrDefault(r, key, defaultValue);
+
+        if (!isSupported) {
+            if (!Objects.equals(val, allowedValue)) {
+                Log.w(TAG, key.getName() + " is not supported; ignoring requested value " + val);
+            }
+            return null;
+        }
+
+        return val;
+    }
 }
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
index 79287c13..a10a2af 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
@@ -24,9 +24,18 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.legacy.ParameterUtils.WeightedRectangle;
+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.*;
 
@@ -54,6 +63,11 @@
 
         CameraMetadataNative result = new CameraMetadataNative();
 
+        Rect activeArraySize = characteristics.get(
+                CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArraySize,
+                request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
+
         /*
          * control
          */
@@ -66,24 +80,34 @@
         /*
          * control.ae*
          */
-        mapAe(result, /*out*/params);
+        mapAe(result, characteristics, request, activeArraySize, zoomData, /*out*/params);
 
         // control.awbLock
         result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock());
 
         // control.awbState
-        if (LegacyMetadataMapper.LIE_ABOUT_AWB) {
+        if (LegacyMetadataMapper.LIE_ABOUT_AWB_STATE) {
             // Lie to pass CTS temporarily.
             // TODO: CTS needs to be updated not to query this value
             // for LIMITED devices unless its guaranteed to be available.
             result.set(CaptureResult.CONTROL_AWB_STATE,
                     CameraMetadata.CONTROL_AWB_STATE_CONVERGED);
             // TODO: Read the awb mode from parameters instead
+        }
+
+        if (LegacyMetadataMapper.LIE_ABOUT_AWB) {
             result.set(CaptureResult.CONTROL_AWB_MODE,
                     request.get(CaptureRequest.CONTROL_AWB_MODE));
         }
 
         /*
+         * flash
+         */
+        {
+            // TODO
+        }
+
+        /*
          * lens
          */
         // lens.focalLength
@@ -92,7 +116,7 @@
         /*
          * scaler
          */
-        mapScaler(result, characteristics, request, previewSize, params);
+        mapScaler(result, zoomData, /*out*/params);
 
         /*
          * sensor
@@ -104,7 +128,9 @@
         return result;
     }
 
-    private static void mapAe(CameraMetadataNative m, /*out*/Parameters p) {
+    private static void mapAe(CameraMetadataNative m,
+            CameraCharacteristics characteristics,
+            CaptureRequest request, Rect activeArray, ZoomData zoomData, /*out*/Parameters p) {
         // control.aeAntiBandingMode
         {
             int antiBandingMode = LegacyMetadataMapper.convertAntiBandingModeOrDefault(
@@ -112,8 +138,31 @@
             m.set(CONTROL_AE_ANTIBANDING_MODE, antiBandingMode);
         }
 
-        // control.aeMode, flash.mode
-        mapAeAndFlashMode(m, p);
+        // control.aeExposureCompensation
+        {
+            m.set(CONTROL_AE_EXPOSURE_COMPENSATION, p.getExposureCompensation());
+        }
+
+        // control.aeLock
+        {
+            boolean lock = p.isAutoExposureLockSupported() ? p.getAutoExposureLock() : false;
+            m.set(CONTROL_AE_LOCK, lock);
+            if (VERBOSE) {
+                Log.v(TAG,
+                        "mapAe - android.control.aeLock = " + lock +
+                        ", supported = " + p.isAutoExposureLockSupported());
+            }
+
+            Boolean requestLock = request.get(CaptureRequest.CONTROL_AE_LOCK);
+            if (requestLock != null && requestLock != lock) {
+                Log.w(TAG,
+                        "mapAe - android.control.aeLock was requested to " + requestLock +
+                        " but resulted in " + lock);
+            }
+        }
+
+        // control.aeMode, flash.mode, flash.state
+        mapAeAndFlashMode(m, characteristics, p);
 
         // control.aeState
         if (LegacyMetadataMapper.LIE_ABOUT_AE_STATE) {
@@ -123,44 +172,102 @@
         }
 
         // control.aeRegions
+        {
+            if (VERBOSE) {
+                String meteringAreas = p.get("metering-areas");
+                Log.v(TAG, "mapAe - parameter dump; metering-areas: " + meteringAreas);
+            }
 
-        /*
-         * TODO: Use the *resulting* crop region to calculate intersection with
-         * metering region
-         *
-         * Report the sensor-relative metering region in the result even
-         * if that's not actually the real thing (similar to how we do it
-         * for zooming)
-         */
-    }
+            MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
+                    zoomData, p.getMeteringAreas(), "AE");
 
-
-    /** Map results for control.aeMode, flash.mode */
-    private static void mapAeAndFlashMode(CameraMetadataNative m, /*out*/Parameters p) {
-        // Default: AE mode on but flash never fires
-        int flashMode = FLASH_MODE_OFF;
-        int aeMode = CONTROL_AE_MODE_ON;
-
-        switch (p.getFlashMode()) {
-            case Parameters.FLASH_MODE_OFF:
-                break; // ok, using default
-            case Parameters.FLASH_MODE_AUTO:
-                aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
-                break;
-            case Parameters.FLASH_MODE_ON:
-                // flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
-                aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
-                break;
-            case Parameters.FLASH_MODE_RED_EYE:
-                aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
-                break;
-            case Parameters.FLASH_MODE_TORCH:
-                flashMode = FLASH_MODE_TORCH;
-                break;
-            default:
-                Log.w(TAG, "mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
+            m.set(CONTROL_AE_REGIONS, meteringRectArray);
         }
 
+        // control.afRegions
+        {
+            if (VERBOSE) {
+                String focusAreas = p.get("focus-areas");
+                Log.v(TAG, "mapAe - parameter dump; focus-areas: " + focusAreas);
+            }
+
+            MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
+                    zoomData, p.getFocusAreas(), "AF");
+
+            m.set(CONTROL_AF_REGIONS, meteringRectArray);
+        }
+
+        // control.awbLock
+        {
+            boolean lock = p.isAutoWhiteBalanceLockSupported() ?
+                    p.getAutoWhiteBalanceLock() : false;
+            m.set(CONTROL_AWB_LOCK, lock);
+        }
+    }
+
+    private static MeteringRectangle[] getMeteringRectangles(Rect activeArray, ZoomData zoomData,
+            List<Camera.Area> meteringAreaList, String regionName) {
+        List<MeteringRectangle> meteringRectList = new ArrayList<>();
+        if (meteringAreaList != null) {
+            for (Camera.Area area : meteringAreaList) {
+                WeightedRectangle rect =
+                        ParameterUtils.convertCameraAreaToActiveArrayRectangle(
+                                activeArray, zoomData, area);
+
+                meteringRectList.add(rect.toMetering());
+            }
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG,
+                    "Metering rectangles for " + regionName + ": "
+                     + ListUtils.listToString(meteringRectList));
+        }
+
+        return meteringRectList.toArray(new MeteringRectangle[0]);
+    }
+
+    /** Map results for control.aeMode, flash.mode, flash.state */
+    private static void mapAeAndFlashMode(CameraMetadataNative m,
+            CameraCharacteristics characteristics, Parameters p) {
+        // Default: AE mode on but flash never fires
+        int flashMode = FLASH_MODE_OFF;
+        // If there is no flash on this camera, the state is always unavailable
+        // , otherwise it's only known for TORCH/SINGLE modes
+        Integer flashState = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
+                ? null : FLASH_STATE_UNAVAILABLE;
+        int aeMode = CONTROL_AE_MODE_ON;
+
+        String flashModeSetting = p.getFlashMode();
+
+        if (flashModeSetting != null) {
+            switch (flashModeSetting) {
+                case Parameters.FLASH_MODE_OFF:
+                    break; // ok, using default
+                case Parameters.FLASH_MODE_AUTO:
+                    aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
+                    break;
+                case Parameters.FLASH_MODE_ON:
+                    // flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
+                    flashMode = FLASH_MODE_SINGLE;
+                    aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
+                    flashState = FLASH_STATE_FIRED;
+                    break;
+                case Parameters.FLASH_MODE_RED_EYE:
+                    aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
+                    break;
+                case Parameters.FLASH_MODE_TORCH:
+                    flashMode = FLASH_MODE_TORCH;
+                    flashState = FLASH_STATE_FIRED;
+                    break;
+                default:
+                    Log.w(TAG,
+                            "mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
+            }
+        }
+
+        // flash.state
+        m.set(FLASH_STATE, flashState);
         // flash.mode
         m.set(FLASH_MODE, flashMode);
         // control.aeMode
@@ -169,33 +276,13 @@
 
     /** Map results for scaler.* */
     private static void mapScaler(CameraMetadataNative m,
-            CameraCharacteristics characteristics,
-            CaptureRequest request,
-            Size previewSize,
+            ZoomData zoomData,
             /*out*/Parameters p) {
         /*
          * scaler.cropRegion
          */
         {
-            Rect activeArraySize = characteristics.get(
-                    CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-            Rect activeArraySizeOnly = new Rect(
-                    /*left*/0, /*top*/0,
-                    activeArraySize.width(), activeArraySize.height());
-
-            Rect userCropRegion = request.get(CaptureRequest.SCALER_CROP_REGION);
-
-            if (userCropRegion == null) {
-                userCropRegion = activeArraySizeOnly;
-            }
-
-            Rect reportedCropRegion = new Rect();
-            Rect previewCropRegion = new Rect();
-            ParameterUtils.getClosestAvailableZoomCrop(p, activeArraySizeOnly,
-                    previewSize, userCropRegion,
-                    /*out*/reportedCropRegion, /*out*/previewCropRegion);
-
-            m.set(SCALER_CROP_REGION, reportedCropRegion);
+            m.set(SCALER_CROP_REGION, zoomData.reportedCrop);
         }
     }
 }
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
index c3b1bbd..efd12f2 100644
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
@@ -17,9 +17,15 @@
 package android.hardware.camera2.legacy;
 
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.Camera;
+import android.hardware.Camera.Area;
+import android.hardware.camera2.legacy.ParameterUtils.MeteringData;
+import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.utils.ListUtils;
 import android.hardware.camera2.utils.ParamsUtils;
 import android.hardware.camera2.utils.SizeAreaComparator;
@@ -38,6 +44,181 @@
  * Various utilities for dealing with camera API1 parameters.
  */
 public class ParameterUtils {
+    /** Upper/left minimal point of a normalized rectangle */
+    public static final int NORMALIZED_RECTANGLE_MIN = -1000;
+    /** Lower/right maximal point of a normalized rectangle */
+    public static final int NORMALIZED_RECTANGLE_MAX = 1000;
+    /** The default normalized rectangle spans the entire size of the preview viewport */
+    public static final Rect NORMALIZED_RECTANGLE_DEFAULT = new Rect(
+            NORMALIZED_RECTANGLE_MIN,
+            NORMALIZED_RECTANGLE_MIN,
+            NORMALIZED_RECTANGLE_MAX,
+            NORMALIZED_RECTANGLE_MAX);
+    /** The default normalized area uses the default normalized rectangle with a weight=1 */
+    public static final Camera.Area CAMERA_AREA_DEFAULT =
+            new Camera.Area(new Rect(NORMALIZED_RECTANGLE_DEFAULT),
+                            /*weight*/1);
+    /** Empty rectangle {@code 0x0+0,0} */
+    public static final Rect RECTANGLE_EMPTY =
+            new Rect(/*left*/0, /*top*/0, /*right*/0, /*bottom*/0);
+
+    /**
+     * Calculate effective/reported zoom data from a user-specified crop region.
+     */
+    public static class ZoomData {
+        /** Zoom index used by {@link Camera.Parameters#setZoom} */
+        public final int zoomIndex;
+        /** Effective crop-region given the zoom index, coordinates relative to active-array */
+        public final Rect previewCrop;
+        /** Reported crop-region given the zoom index, coordinates relative to active-array */
+        public final Rect reportedCrop;
+
+        public ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop) {
+            this.zoomIndex = zoomIndex;
+            this.previewCrop = previewCrop;
+            this.reportedCrop = reportedCrop;
+        }
+    }
+
+    /**
+     * Calculate effective/reported metering data from a user-specified metering region.
+     */
+    public static class MeteringData {
+        /**
+         * The metering area scaled to the range of [-1000, 1000].
+         * <p>Values outside of this range are clipped to be within the range.</p>
+         */
+        public final Camera.Area meteringArea;
+        /**
+         * Effective preview metering region, coordinates relative to active-array.
+         *
+         * <p>Clipped to fit inside of the (effective) preview crop region.</p>
+         */
+        public final Rect previewMetering;
+        /**
+         * Reported metering region, coordinates relative to active-array.
+         *
+         * <p>Clipped to fit inside of the (reported) resulting crop region.</p>
+         */
+        public final Rect reportedMetering;
+
+        public MeteringData(Area meteringArea, Rect previewMetering, Rect reportedMetering) {
+            this.meteringArea = meteringArea;
+            this.previewMetering = previewMetering;
+            this.reportedMetering = reportedMetering;
+        }
+    }
+
+    /**
+     * A weighted rectangle is an arbitrary rectangle (the coordinate system is unknown) with an
+     * arbitrary weight.
+     *
+     * <p>The user of this class must know what the coordinate system ahead of time; it's
+     * then possible to convert to a more concrete type such as a metering rectangle or a face.
+     * </p>
+     *
+     * <p>When converting to a more concrete type, out-of-range values are clipped; this prevents
+     * possible illegal argument exceptions being thrown at runtime.</p>
+     */
+    public static class WeightedRectangle {
+        /** Arbitrary rectangle (the range is user-defined); never {@code null}. */
+        public final Rect rect;
+        /** Arbitrary weight (the range is user-defined). */
+        public final int weight;
+
+        /**
+         * Create a new weighted-rectangle from a non-{@code null} rectangle; the {@code weight}
+         * can be unbounded.
+         */
+        public WeightedRectangle(Rect rect, int weight) {
+            this.rect = checkNotNull(rect, "rect must not be null");
+            this.weight = weight;
+        }
+
+        /**
+         * Convert to a metering rectangle, clipping any of the values to stay within range.
+         *
+         * <p>If values are clipped, a warning is printed to logcat.</p>
+         *
+         * @return a new metering rectangle
+         */
+        public MeteringRectangle toMetering() {
+            int weight = clip(this.weight,
+                    MeteringRectangle.METERING_WEIGHT_MIN,
+                    MeteringRectangle.METERING_WEIGHT_MAX,
+                    rect,
+                    "weight");
+
+            int x = clipLower(rect.left, /*lo*/0, rect, "left");
+            int y = clipLower(rect.top, /*lo*/0, rect, "top");
+            int w = clipLower(rect.width(), /*lo*/0, rect, "width");
+            int h = clipLower(rect.height(), /*lo*/0, rect, "height");
+
+            return new MeteringRectangle(x, y, w, h, weight);
+        }
+
+        /**
+         * Convert to a face; the rect is considered to be the bounds, and the weight
+         * is considered to be the score.
+         *
+         * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
+         * the score is clipped first and a warning is printed to logcat.</p>
+         *
+         * <p>All other parameters are passed-through as-is.</p>
+         *
+         * @return a new face with the optional features set
+         */
+        public Face toFace(
+                int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) {
+            int score = clip(weight,
+                    Face.SCORE_MIN,
+                    Face.SCORE_MAX,
+                    rect,
+                    "score");
+
+            return new Face(rect, score, id, leftEyePosition, rightEyePosition, mouthPosition);
+        }
+
+        /**
+         * Convert to a face; the rect is considered to be the bounds, and the weight
+         * is considered to be the score.
+         *
+         * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
+         * the score is clipped first and a warning is printed to logcat.</p>
+         *
+         * <p>All other parameters are passed-through as-is.</p>
+         *
+         * @return a new face without the optional features
+         */
+        public Face toFace() {
+            int score = clip(weight,
+                    Face.SCORE_MIN,
+                    Face.SCORE_MAX,
+                    rect,
+                    "score");
+
+            return new Face(rect, score);
+        }
+
+        private static int clipLower(int value, int lo, Rect rect, String name) {
+            return clip(value, lo, /*hi*/Integer.MAX_VALUE, rect, name);
+        }
+
+        private static int clip(int value, int lo, int hi, Rect rect, String name) {
+            if (value < lo) {
+                Log.w(TAG, "toMetering - Rectangle " + rect + " "
+                        + name + " too small, clip to " + lo);
+                value = lo;
+            } else if (value > hi) {
+                Log.w(TAG, "toMetering - Rectangle " + rect + " "
+                        + name + " too small, clip to " + hi);
+                value = hi;
+            }
+
+            return value;
+        }
+    }
+
     private static final String TAG = "ParameterUtils";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
@@ -100,6 +281,35 @@
     }
 
     /**
+     * Convert a camera area list into a human-readable string
+     * @param areaList a list of areas (null is ok)
+     */
+    public static String stringFromAreaList(List<Camera.Area> areaList) {
+        StringBuilder sb = new StringBuilder();
+
+        if (areaList == null) {
+            return null;
+        }
+
+        int i = 0;
+        for (Camera.Area area : areaList) {
+            if (area == null) {
+                sb.append("null");
+            } else {
+                sb.append(stringFromArea(area));
+            }
+
+            if (i != areaList.size() - 1) {
+                sb.append(", ");
+            }
+
+            i++;
+        }
+
+        return sb.toString();
+    }
+
+    /**
      * Calculate the closest zoom index for the user-requested crop region by rounding
      * up to the closest (largest or equal) possible zoom crop.
      *
@@ -487,6 +697,217 @@
         return new SizeF(zoomRatioWidth, zoomRatioHeight);
     }
 
+    /**
+     * Convert the user-specified crop region into zoom data; which can be used
+     * to set the parameters to a specific zoom index, or to report back to the user what the
+     * actual zoom was, or for other calculations requiring the current preview crop region.
+     *
+     * <p>None of the parameters are mutated.</p>
+     *
+     * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+     * @param cropRegion the user-specified crop region
+     * @param previewSize the current preview size (in pixels)
+     * @param params the current camera parameters (not mutated)
+     *
+     * @return the zoom index, and the effective/reported crop regions (relative to active array)
+     */
+    public static ZoomData convertScalerCropRegion(Rect activeArraySize, Rect
+            cropRegion, Size previewSize, Camera.Parameters params) {
+        Rect activeArraySizeOnly = new Rect(
+                /*left*/0, /*top*/0,
+                activeArraySize.width(), activeArraySize.height());
+
+        Rect userCropRegion = cropRegion;
+
+        if (userCropRegion == null) {
+            userCropRegion = activeArraySizeOnly;
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, "convertScalerCropRegion - user crop region was " + userCropRegion);
+        }
+
+        final Rect reportedCropRegion = new Rect();
+        final Rect previewCropRegion = new Rect();
+        final int zoomIdx = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly,
+                previewSize, userCropRegion,
+                /*out*/reportedCropRegion, /*out*/previewCropRegion);
+
+        if (VERBOSE) {
+            Log.v(TAG, "convertScalerCropRegion - zoom calculated to: " +
+                    "zoomIndex = " + zoomIdx +
+                    ", reported crop region = " + reportedCropRegion +
+                    ", preview crop region = " + previewCropRegion);
+        }
+
+        return new ZoomData(zoomIdx, previewCropRegion, reportedCropRegion);
+    }
+
+    /**
+     * Calculate the actual/effective/reported normalized rectangle data from a metering
+     * rectangle.
+     *
+     * <p>If any of the rectangles are out-of-range of their intended bounding box,
+     * the {@link #RECTANGLE_EMPTY empty rectangle} is substituted instead
+     * (with a weight of {@code 0}).</p>
+     *
+     * <p>The metering rectangle is bound by the crop region (effective/reported respectively).
+     * The metering {@link Camera.Area area} is bound by {@code [-1000, 1000]}.</p>
+     *
+     * <p>No parameters are mutated; returns the new metering data.</p>
+     *
+     * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+     * @param meteringRect the user-specified metering rectangle
+     * @param zoomData the calculated zoom data corresponding to this request
+     *
+     * @return the metering area, the reported/effective metering rectangles
+     */
+    public static MeteringData convertMeteringRectangleToLegacy(
+            Rect activeArray, MeteringRectangle meteringRect, ZoomData zoomData) {
+        Rect previewCrop = zoomData.previewCrop;
+
+        float scaleW = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f /
+                previewCrop.width();
+        float scaleH = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f /
+                previewCrop.height();
+
+        Matrix transform = new Matrix();
+        // Move the preview crop so that top,left is at (0,0), otherwise after scaling
+        // the corner bounds will be outside of [-1000, 1000]
+        transform.setTranslate(-previewCrop.left, -previewCrop.top);
+        // Scale into [0, 2000] range about the center of the preview
+        transform.postScale(scaleW, scaleH);
+        // Move so that top left of a typical rect is at [-1000, -1000]
+        transform.postTranslate(/*dx*/NORMALIZED_RECTANGLE_MIN, /*dy*/NORMALIZED_RECTANGLE_MIN);
+
+        /*
+         * Calculate the preview metering region (effective), and the camera1 api
+         * normalized metering region.
+         */
+        Rect normalizedRegionUnbounded = ParamsUtils.mapRect(transform, meteringRect.getRect());
+
+        /*
+         * Try to intersect normalized area with [-1000, 1000] rectangle; otherwise
+         * it's completely out of range
+         */
+        Rect normalizedIntersected = new Rect(normalizedRegionUnbounded);
+
+        Camera.Area meteringArea;
+        if (!normalizedIntersected.intersect(NORMALIZED_RECTANGLE_DEFAULT)) {
+            Log.w(TAG,
+                    "convertMeteringRectangleToLegacy - metering rectangle too small, " +
+                    "no metering will be done");
+            normalizedIntersected.set(RECTANGLE_EMPTY);
+            meteringArea = new Camera.Area(RECTANGLE_EMPTY,
+                    MeteringRectangle.METERING_WEIGHT_DONT_CARE);
+        } else {
+            meteringArea = new Camera.Area(normalizedIntersected,
+                    meteringRect.getMeteringWeight());
+        }
+
+        /*
+         * Calculate effective preview metering region
+         */
+        Rect previewMetering = meteringRect.getRect();
+        if (!previewMetering.intersect(previewCrop)) {
+            previewMetering.set(RECTANGLE_EMPTY);
+        }
+
+        /*
+         * Calculate effective reported metering region
+         * - Transform the calculated metering area back into active array space
+         * - Clip it to be a subset of the reported crop region
+         */
+        Rect reportedMetering;
+        {
+            Camera.Area normalizedAreaUnbounded = new Camera.Area(
+                    normalizedRegionUnbounded, meteringRect.getMeteringWeight());
+            WeightedRectangle reportedMeteringRect = convertCameraAreaToActiveArrayRectangle(
+                    activeArray, zoomData, normalizedAreaUnbounded, /*usePreviewCrop*/false);
+            reportedMetering = reportedMeteringRect.rect;
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, String.format(
+                    "convertMeteringRectangleToLegacy - activeArray = %s, meteringRect = %s, " +
+                    "previewCrop = %s, meteringArea = %s, previewMetering = %s, " +
+                    "reportedMetering = %s, normalizedRegionUnbounded = %s",
+                    activeArray, meteringRect,
+                    previewCrop, stringFromArea(meteringArea), previewMetering,
+                    reportedMetering, normalizedRegionUnbounded));
+        }
+
+        return new MeteringData(meteringArea, previewMetering, reportedMetering);
+    }
+
+    /**
+     * Convert the normalized camera area from [-1000, 1000] coordinate space
+     * into the active array-based coordinate space.
+     *
+     * <p>Values out of range are clipped to be within the resulting (reported) crop
+     * region. It is possible to have values larger than the preview crop.</p>
+     *
+     * <p>Weights out of range of [0, 1000] are clipped to be within the range.</p>
+     *
+     * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+     * @param zoomData the calculated zoom data corresponding to this request
+     * @param area the normalized camera area
+     *
+     * @return the weighed rectangle in active array coordinate space, with the weight
+     */
+    public static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
+            Rect activeArray, ZoomData zoomData, Camera.Area area) {
+        return convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, area,
+                /*usePreviewCrop*/true);
+    }
+
+    private static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
+            Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop) {
+        Rect previewCrop = zoomData.previewCrop;
+        Rect reportedCrop = zoomData.reportedCrop;
+
+        float scaleW = previewCrop.width() * 1.0f /
+                (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN);
+        float scaleH = previewCrop.height() * 1.0f /
+                (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN);
+
+        /*
+         * Calculate the reported metering region from the non-intersected normalized region
+         * by scaling and translating back into active array-relative coordinates.
+         */
+        Matrix transform = new Matrix();
+
+        // Move top left from (-1000, -1000) to (0, 0)
+        transform.setTranslate(/*dx*/NORMALIZED_RECTANGLE_MAX, /*dy*/NORMALIZED_RECTANGLE_MAX);
+
+        // Scale from [0, 2000] back into the preview rectangle
+        transform.postScale(scaleW, scaleH);
+
+        // Move the rect so that the [-1000,-1000] point ends up at the preview [left, top]
+        transform.postTranslate(previewCrop.left, previewCrop.top);
+
+        Rect cropToIntersectAgainst = usePreviewCrop ? previewCrop : reportedCrop;
+
+        // Now apply the transformation backwards to get the reported metering region
+        Rect reportedMetering = ParamsUtils.mapRect(transform, area.rect);
+        // Intersect it with the crop region, to avoid reporting out-of-bounds
+        // metering regions
+        if (!reportedMetering.intersect(cropToIntersectAgainst)) {
+            reportedMetering.set(RECTANGLE_EMPTY);
+        }
+
+        int weight = area.weight;
+        if (weight < MeteringRectangle.METERING_WEIGHT_MIN) {
+            Log.w(TAG,
+                    "convertCameraAreaToMeteringRectangle - rectangle "
+                            + stringFromArea(area) + " has too small weight, clip to 0");
+            weight = 0;
+        }
+
+        return new WeightedRectangle(reportedMetering, area.weight);
+    }
+
+
     private ParameterUtils() {
         throw new AssertionError();
     }
diff --git a/core/java/android/hardware/camera2/params/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java
index 93fd053..b1cea57 100644
--- a/core/java/android/hardware/camera2/params/MeteringRectangle.java
+++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java
@@ -100,6 +100,8 @@
     /**
      * Create a new metering rectangle.
      *
+     * <p>The point {@code xy}'s data is copied; the reference is not retained.</p>
+     *
      * @param xy a non-{@code null} {@link Point} with both x,y >= 0
      * @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0
      * @param meteringWeight weight >= 0
@@ -121,6 +123,8 @@
     /**
      * Create a new metering rectangle.
      *
+     * <p>The rectangle data is copied; the reference is not retained.</p>
+     *
      * @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0
      * @param meteringWeight weight >= 0
      *
@@ -185,7 +189,7 @@
     /**
      * Convenience method to create the upper-left (X,Y) coordinate as a {@link Point}.
      *
-     * @return {@code (x,y)} point with both x,y >= 0
+     * @return a new {@code (x,y)} {@link Point} with both x,y >= 0
      */
     public Point getUpperLeftPoint() {
         return new Point(mX, mY);
@@ -196,7 +200,7 @@
      *
      * <p>This strips away the X,Y,weight from the rectangle.</p>
      *
-     * @return a Size with non-negative width and height
+     * @return a new {@link Size} with non-negative width and height
      */
     public Size getSize() {
         return new Size(mWidth, mHeight);
@@ -207,7 +211,7 @@
      *
      * <p>This strips away the weight from the rectangle.</p>
      *
-     * @return a {@link Rect} with non-negative x1, y1, x2, y2
+     * @return a new {@link Rect} with non-negative x1, y1, x2, y2
      */
     public Rect getRect() {
         return new Rect(mX, mY, mX + mWidth, mY + mHeight);
@@ -250,4 +254,16 @@
     public int hashCode() {
         return HashCodeHelpers.hashCode(mX, mY, mWidth, mHeight, mWeight);
     }
+
+    /**
+     * Return the metering rectangle as a string representation
+     * {@code "(x:%d, y:%d, w:%d, h:%d, wt:%d)"} where each {@code %d} respectively represents
+     * the x, y, width, height, and weight points.
+     *
+     * @return string representation of the metering rectangle
+     */
+    @Override
+    public String toString() {
+        return String.format("(x:%d, y:%d, w:%d, h:%d, wt:%d)", mX, mY, mWidth, mHeight, mWeight);
+    }
 }
diff --git a/core/java/android/hardware/camera2/utils/ParamsUtils.java b/core/java/android/hardware/camera2/utils/ParamsUtils.java
index cd39f71..232a4f6 100644
--- a/core/java/android/hardware/camera2/utils/ParamsUtils.java
+++ b/core/java/android/hardware/camera2/utils/ParamsUtils.java
@@ -16,8 +16,10 @@
 
 package android.hardware.camera2.utils;
 
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.Rational;
 import android.util.Size;
 
 import static com.android.internal.util.Preconditions.*;
@@ -27,6 +29,9 @@
  */
 public class ParamsUtils {
 
+    /** Arbitrary denominator used to estimate floats as rationals */
+    private static final int RATIONAL_DENOMINATOR = 1000000; // 1million
+
     /**
      * Create a {@link Rect} from a {@code Size} by creating a new rectangle with
      * left, top = {@code (0, 0)} and right, bottom = {@code (width, height)}
@@ -45,7 +50,9 @@
 
     /**
      * Create a {@link Rect} from a {@code RectF} by creating a new rectangle with
-     * each corner (left, top, right, bottom) rounded towards the nearest integer value.
+     * each corner (left, top, right, bottom) rounded towards the nearest integer bounding box.
+     *
+     * <p>In particular (left, top) is floored, and (right, bottom) is ceiled.</p>
      *
      * @param size a non-{@code null} rect
      *
@@ -57,12 +64,34 @@
         checkNotNull(rect, "rect must not be null");
 
         Rect r = new Rect();
-        rect.round(r);
+        rect.roundOut(r);
 
         return r;
     }
 
     /**
+     * Map the rectangle in {@code rect} with the transform in {@code transform} into
+     * a new rectangle, with each corner (left, top, right, bottom) rounded towards the nearest
+     * integer bounding box.
+     *
+     * <p>None of the arguments are mutated.</p>
+     *
+     * @param transform a non-{@code null} transformation matrix
+     * @param rect a non-{@code null} rectangle
+     * @return a new rectangle that was transformed by {@code transform}
+     *
+     * @throws NullPointerException if any of the args were {@code null}
+     */
+    public static Rect mapRect(Matrix transform, Rect rect) {
+        checkNotNull(transform, "transform must not be null");
+        checkNotNull(rect, "rect must not be null");
+
+        RectF rectF = new RectF(rect);
+        transform.mapRect(rectF);
+        return createRect(rectF);
+    }
+
+    /**
      * Create a {@link Size} from a {@code Rect} by creating a new size whose width
      * and height are the same as the rectangle's width and heights.
      *
@@ -79,10 +108,59 @@
     }
 
     /**
-     * Convert an integral rectangle ({@code size}) to a floating point rectangle
+     * Create a {@link Rational} value by approximating the float value as a rational.
+     *
+     * <p>Floating points too large to be represented as an integer will be converted to
+     * to {@link Integer#MAX_VALUE}; floating points too small to be represented as an integer
+     * will be converted to {@link Integer#MIN_VALUE}.</p>
+     *
+     * @param value a floating point value
+     * @return the rational representation of the float
+     */
+    public static Rational createRational(float value) {
+        if (Float.isNaN(value)) {
+            return Rational.NaN;
+        } else if (value == Float.POSITIVE_INFINITY) {
+            return Rational.POSITIVE_INFINITY;
+        } else if (value == Float.NEGATIVE_INFINITY) {
+            return Rational.NEGATIVE_INFINITY;
+        } else if (value == 0.0f) {
+            return Rational.ZERO;
+        }
+
+        // normal finite value: approximate it
+
+        /*
+         * Start out trying to approximate with denominator = 1million,
+         * but if the numerator doesn't fit into an Int then keep making the denominator
+         * smaller until it does.
+         */
+        int den = RATIONAL_DENOMINATOR;
+        float numF;
+        do {
+            numF = value * den;
+
+            if ((numF > Integer.MIN_VALUE && numF < Integer.MAX_VALUE) || (den == 1)) {
+                break;
+            }
+
+            den /= 10;
+        } while (true);
+
+        /*
+         *  By float -> int narrowing conversion in JLS 5.1.3, this will automatically become
+         *  MIN_VALUE or MAX_VALUE if numF is too small/large to be represented by an integer
+         */
+        int num = (int) numF;
+
+        return new Rational(num, den);
+     }
+
+    /**
+     * Convert an integral rectangle ({@code source}) to a floating point rectangle
      * ({@code destination}) in-place.
      *
-     * @param size the originating integer rectangle will be read from here
+     * @param source the originating integer rectangle will be read from here
      * @param destination the resulting floating point rectangle will be written out to here
      *
      * @throws NullPointerException if {@code rect} was {@code null}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/hardware/location/ActivityChangedEvent.aidl
similarity index 84%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/hardware/location/ActivityChangedEvent.aidl
index 3da1321..21f2445 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/hardware/location/ActivityChangedEvent.aidl
@@ -11,9 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package android.bluetooth.le;
+package android.hardware.location;
 
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable ActivityChangedEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java
new file mode 100644
index 0000000..0a89207
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityChangedEvent.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.hardware.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ *
+ * @hide
+ */
+public class ActivityChangedEvent implements Parcelable {
+    private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+    public ActivityChangedEvent(ActivityRecognitionEvent[] activityRecognitionEvents) {
+        if (activityRecognitionEvents == null) {
+            throw new InvalidParameterException(
+                    "Parameter 'activityRecognitionEvents' must not be null.");
+        }
+
+        mActivityRecognitionEvents = Arrays.asList(activityRecognitionEvents);
+    }
+
+    @NonNull
+    public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+        return mActivityRecognitionEvents;
+    }
+
+    public static final Creator<ActivityChangedEvent> CREATOR =
+            new Creator<ActivityChangedEvent>() {
+        @Override
+        public ActivityChangedEvent createFromParcel(Parcel source) {
+            int activityRecognitionEventsLength = source.readInt();
+            ActivityRecognitionEvent[] activityRecognitionEvents =
+                    new ActivityRecognitionEvent[activityRecognitionEventsLength];
+            source.readTypedArray(activityRecognitionEvents, ActivityRecognitionEvent.CREATOR);
+
+            return new ActivityChangedEvent(activityRecognitionEvents);
+        }
+
+        @Override
+        public ActivityChangedEvent[] newArray(int size) {
+            return new ActivityChangedEvent[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        ActivityRecognitionEvent[] activityRecognitionEventArray =
+                mActivityRecognitionEvents.toArray(new ActivityRecognitionEvent[0]);
+        parcel.writeInt(activityRecognitionEventArray.length);
+        parcel.writeTypedArray(activityRecognitionEventArray, flags);
+    }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..5aeb899
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ *
+ * @hide
+ */
+public class ActivityRecognitionEvent implements Parcelable {
+    private final String mActivity;
+    private final int mEventType;
+    private final long mTimestampNs;
+
+    public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+        mActivity = activity;
+        mEventType = eventType;
+        mTimestampNs = timestampNs;
+    }
+
+    public String getActivity() {
+        return mActivity;
+    }
+
+    public int getEventType() {
+        return mEventType;
+    }
+
+    public long getTimestampNs() {
+        return mTimestampNs;
+    }
+
+    public static final Creator<ActivityRecognitionEvent> CREATOR =
+            new Creator<ActivityRecognitionEvent>() {
+        @Override
+        public ActivityRecognitionEvent createFromParcel(Parcel source) {
+            String activity = source.readString();
+            int eventType = source.readInt();
+            long timestampNs = source.readLong();
+
+            return new ActivityRecognitionEvent(activity, eventType, timestampNs);
+        }
+
+        @Override
+        public ActivityRecognitionEvent[] newArray(int size) {
+            return new ActivityRecognitionEvent[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mActivity);
+        parcel.writeInt(mEventType);
+        parcel.writeLong(mTimestampNs);
+    }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
new file mode 100644
index 0000000..a4ce4ac
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity
+ * Recognition HAL.
+ *
+ * @hide
+ */
+public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub {
+    private static final String TAG = "ActivityRecognitionHardware";
+
+    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+    private static final int INVALID_ACTIVITY_TYPE = -1;
+    private static final int NATIVE_SUCCESS_RESULT = 0;
+
+    private static ActivityRecognitionHardware sSingletonInstance = null;
+    private static final Object sSingletonInstanceLock = new Object();
+
+    private final Context mContext;
+    private final String[] mSupportedActivities;
+
+    private final RemoteCallbackList<IActivityRecognitionHardwareSink> mSinks =
+            new RemoteCallbackList<IActivityRecognitionHardwareSink>();
+
+    private static class Event {
+        public int activity;
+        public int type;
+        public long timestamp;
+    }
+
+    private ActivityRecognitionHardware(Context context) {
+        nativeInitialize();
+
+        mContext = context;
+        mSupportedActivities = fetchSupportedActivities();
+    }
+
+    public static ActivityRecognitionHardware getInstance(Context context) {
+        synchronized (sSingletonInstanceLock) {
+            if (sSingletonInstance == null) {
+                sSingletonInstance = new ActivityRecognitionHardware(context);
+            }
+
+            return sSingletonInstance;
+        }
+    }
+
+    public static boolean isSupported() {
+        return nativeIsSupported();
+    }
+
+    @Override
+    public String[] getSupportedActivities() {
+        checkPermissions();
+        return mSupportedActivities;
+    }
+
+    @Override
+    public boolean isActivitySupported(String activity) {
+        checkPermissions();
+        int activityType = getActivityType(activity);
+        return activityType != INVALID_ACTIVITY_TYPE;
+    }
+
+    @Override
+    public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+        checkPermissions();
+        return mSinks.register(sink);
+    }
+
+    @Override
+    public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+        checkPermissions();
+        return mSinks.unregister(sink);
+    }
+
+    @Override
+    public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
+        checkPermissions();
+
+        int activityType = getActivityType(activity);
+        if (activityType == INVALID_ACTIVITY_TYPE) {
+            return false;
+        }
+
+        int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
+        return result == NATIVE_SUCCESS_RESULT;
+    }
+
+    @Override
+    public boolean disableActivityEvent(String activity, int eventType) {
+        checkPermissions();
+
+        int activityType = getActivityType(activity);
+        if (activityType == INVALID_ACTIVITY_TYPE) {
+            return false;
+        }
+
+        int result = nativeDisableActivityEvent(activityType, eventType);
+        return result == NATIVE_SUCCESS_RESULT;
+    }
+
+    @Override
+    public boolean flush() {
+        checkPermissions();
+        int result = nativeFlush();
+        return result == NATIVE_SUCCESS_RESULT;
+    }
+
+    /**
+     * Called by the Activity-Recognition HAL.
+     */
+    private void onActivityChanged(Event[] events) {
+        int size = mSinks.beginBroadcast();
+        if (size == 0 || events == null || events.length == 0) {
+            return;
+        }
+
+        int eventsLength = events.length;
+        ActivityRecognitionEvent activityRecognitionEventArray[] =
+                new ActivityRecognitionEvent[eventsLength];
+        for (int i = 0; i < eventsLength; ++i) {
+            Event event = events[i];
+            String activityName = getActivityName(event.activity);
+            activityRecognitionEventArray[i] =
+                    new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
+        }
+        ActivityChangedEvent activityChangedEvent =
+                new ActivityChangedEvent(activityRecognitionEventArray);
+
+        for (int i = 0; i < size; ++i) {
+            IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
+            try {
+                sink.onActivityChanged(activityChangedEvent);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error delivering activity changed event.", e);
+            }
+        }
+        mSinks.finishBroadcast();
+
+    }
+
+    private String getActivityName(int activityType) {
+        if (activityType < 0 || activityType >= mSupportedActivities.length) {
+            String message = String.format(
+                    "Invalid ActivityType: %d, SupportedActivities: %d",
+                    activityType,
+                    mSupportedActivities.length);
+            Log.e(TAG, message);
+            return null;
+        }
+
+        return mSupportedActivities[activityType];
+    }
+
+    private int getActivityType(String activity) {
+        if (TextUtils.isEmpty(activity)) {
+            return INVALID_ACTIVITY_TYPE;
+        }
+
+        int supporteActivitiesLength = mSupportedActivities.length;
+        for (int i = 0; i < supporteActivitiesLength; ++i) {
+            if (activity.equals(mSupportedActivities[i])) {
+                return i;
+            }
+        }
+
+        return INVALID_ACTIVITY_TYPE;
+    }
+
+    private void checkPermissions() {
+        String message = String.format(
+                "Permission '%s' not granted to access ActivityRecognitionHardware",
+                HARDWARE_PERMISSION);
+        mContext.enforceCallingPermission(HARDWARE_PERMISSION, message);
+    }
+
+    private static String[] fetchSupportedActivities() {
+        String[] supportedActivities = nativeGetSupportedActivities();
+        if (supportedActivities != null) {
+            return supportedActivities;
+        }
+
+        return new String[0];
+    }
+
+    // native bindings
+    static { nativeClassInit(); }
+
+    private static native void nativeClassInit();
+    private static native void nativeInitialize();
+    private static native void nativeRelease();
+    private static native boolean nativeIsSupported();
+    private static native String[] nativeGetSupportedActivities();
+    private static native int nativeEnableActivityEvent(
+            int activityType,
+            int eventType,
+            long reportLatenceNs);
+    private static native int nativeDisableActivityEvent(int activityType, int eventType);
+    private static native int nativeFlush();
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardware.aidl b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
new file mode 100644
index 0000000..bc6b183
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/license/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.hardware.location.IActivityRecognitionHardwareSink;
+
+/**
+ * Activity Recognition Hardware provider interface.
+ * This interface can be used to implement hardware based activity recognition.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardware {
+    /**
+     * Gets an array of supported activities by hardware.
+     */
+    String[] getSupportedActivities();
+
+    /**
+     * Returns true if the given activity is supported, false otherwise.
+     */
+    boolean isActivitySupported(in String activityType);
+
+    /**
+     * Registers a sink with Hardware Activity-Recognition.
+     */
+    boolean registerSink(in IActivityRecognitionHardwareSink sink);
+
+    /**
+     * Unregisters a sink with Hardware Activity-Recognition.
+     */
+    boolean unregisterSink(in IActivityRecognitionHardwareSink sink);
+
+    /**
+     * Enables tracking of a given activity/event type, if the activity is supported.
+     */
+    boolean enableActivityEvent(in String activityType, int eventType, long reportLatencyNs);
+
+    /**
+     * Disables tracking of a given activity/eventy type.
+     */
+    boolean disableActivityEvent(in String activityType, int eventType);
+
+    /**
+     * Requests hardware for all the activity events detected up to the given point in time.
+     */
+    boolean flush();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
new file mode 100644
index 0000000..21c8e87
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/license/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.hardware.location.ActivityChangedEvent;
+
+/**
+ * Activity Recognition Hardware provider Sink interface.
+ * This interface can be used to implement sinks to receive activity notifications.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareSink {
+    /**
+     * Activity changed event.
+     */
+    void onActivityChanged(in ActivityChangedEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
new file mode 100644
index 0000000..0507f52
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/license/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware watcher. This interface can be used to receive interfaces to
+ * implementations of {@link IActivityRecognitionHardware}.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareWatcher {
+    /**
+     * Hardware Activity-Recognition availability event.
+     */
+    void onInstanceChanged(in IActivityRecognitionHardware instance);
+}
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 3417de1..8423d09 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -53,6 +53,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
@@ -251,18 +252,6 @@
      */
     public static final int IME_VISIBLE = 0x2;
 
-    /**
-     * The IME does not require cursor/anchor position.
-     */
-    public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0x0;
-
-    /**
-     * Passing this flag into a call to {@link #setCursorAnchorMonitorMode(int)} will result in
-     * the cursor rectangle being provided in screen coordinates to subsequent
-     * {@link #onUpdateCursor(Rect)} callbacks.
-     */
-    public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 0x1;
-
     InputMethodManager mImm;
     
     int mTheme = 0;
@@ -1722,8 +1711,9 @@
      * Called when the application has reported a new location of its text cursor.  This is only
      * called if explicitly requested by the input method.  The default implementation does nothing.
      * @param newCursor The new cursor position, in screen coordinates if the input method calls
-     * {@link #setCursorAnchorMonitorMode} with {@link #CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT}.
-     * Otherwise, this is in local coordinates.
+     * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} with
+     * {@link CursorAnchorInfoRequest#FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES}. Otherwise,
+     * this is in local coordinates.
      */
     public void onUpdateCursor(Rect newCursor) {
         // Intentionally empty
@@ -1741,13 +1731,6 @@
     }
 
     /**
-     * Update the cursor/anthor monitor mode.
-     */
-    public void setCursorAnchorMonitorMode(int monitorMode) {
-        mImm.setCursorAnchorMonitorMode(mToken, monitorMode);
-    }
-
-    /**
      * Close this input method's soft input area, removing it from the display.
      * The input method will continue running, but the user can no longer use
      * it to generate input by touching the screen.
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 79db389..58d0048 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -45,7 +45,7 @@
     protected NetworkInfo mNetworkInfo;
     protected LinkProperties mLinkProperties;
     protected NetworkCapabilities mNetworkCapabilities;
-    protected Network mNetwork = new Network(ConnectivityManager.INVALID_NET_ID);
+    protected Network mNetwork = new Network(ConnectivityManager.NETID_UNSET);
 
     private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
     private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a7e03fc..8de545e 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -532,12 +532,14 @@
     /**
      * @hide
      */
-    public final static int INVALID_NET_ID = 0;
+    public final static int REQUEST_ID_UNSET = 0;
 
     /**
+     * A NetID indicating no Network is selected.
+     * Keep in sync with bionic/libc/dns/include/resolv_netid.h
      * @hide
      */
-    public final static int REQUEST_ID_UNSET = 0;
+    public static final int NETID_UNSET = 0;
 
     private final IConnectivityManager mService;
 
@@ -2508,11 +2510,7 @@
      * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
      */
     public static boolean setProcessDefaultNetwork(Network network) {
-        if (network == null) {
-            return NetworkUtils.unbindProcessToNetwork();
-        } else {
-            return NetworkUtils.bindProcessToNetwork(network.netId);
-        }
+        return NetworkUtils.bindProcessToNetwork(network == null ? NETID_UNSET : network.netId);
     }
 
     /**
@@ -2523,7 +2521,7 @@
      */
     public static Network getProcessDefaultNetwork() {
         int netId = NetworkUtils.getNetworkBoundToProcess();
-        if (netId == 0) return null;
+        if (netId == NETID_UNSET) return null;
         return new Network(netId);
     }
 
@@ -2538,10 +2536,7 @@
      * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}.
      */
     public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
-        if (network == null) {
-            return NetworkUtils.unbindProcessToNetworkForHostResolution();
-        } else {
-            return NetworkUtils.bindProcessToNetworkForHostResolution(network.netId);
-        }
+        return NetworkUtils.bindProcessToNetworkForHostResolution(
+                network == null ? NETID_UNSET : network.netId);
     }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index b9c6491..f61984a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -42,9 +42,6 @@
 /** {@hide} */
 interface IConnectivityManager
 {
-    // Keep this in sync with framework/native/services/connectivitymanager/ConnectivityManager.h
-    void markSocketAsUser(in ParcelFileDescriptor socket, int uid);
-
     NetworkInfo getActiveNetworkInfo();
     NetworkInfo getActiveNetworkInfoForUid(int uid);
     NetworkInfo getNetworkInfo(int networkType);
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index aa1e123..9b95305 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -106,27 +106,16 @@
     public native static String getDhcpError();
 
     /**
-     * Set the SO_MARK of {@code socketfd} to {@code mark}
-     */
-    public native static void markSocket(int socketfd, int mark);
-
-    /**
      * Binds the current process to the network designated by {@code netId}.  All sockets created
      * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
      * {@link Network#getSocketFactory}) will be bound to this network.  Note that if this
      * {@code Network} ever disconnects all sockets created in this way will cease to work.  This
      * is by design so an application doesn't accidentally use sockets it thinks are still bound to
-     * a particular {@code Network}.
+     * a particular {@code Network}.  Passing NETID_UNSET clears the binding.
      */
     public native static boolean bindProcessToNetwork(int netId);
 
     /**
-     * Clear any process specific {@code Network} binding.  This reverts a call to
-     * {@link #bindProcessToNetwork}.
-     */
-    public native static boolean unbindProcessToNetwork();
-
-    /**
      * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
      * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
      */
@@ -134,21 +123,14 @@
 
     /**
      * Binds host resolutions performed by this process to the network designated by {@code netId}.
-     * {@link #bindProcessToNetwork} takes precedence over this setting.
+     * {@link #bindProcessToNetwork} takes precedence over this setting.  Passing NETID_UNSET clears
+     * the binding.
      *
      * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
      */
     public native static boolean bindProcessToNetworkForHostResolution(int netId);
 
     /**
-     * Clears any process specific {@link Network} binding for host resolution.  This does
-     * not clear bindings enacted via {@link #bindProcessToNetwork}.
-     *
-     * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
-     */
-    public native static boolean unbindProcessToNetworkForHostResolution();
-
-    /**
      * Explicitly binds {@code socketfd} to the network designated by {@code netId}.  This
      * overrides any binding via {@link #bindProcessToNetwork}.
      */
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
index 691afdd..1e1ad9e 100644
--- a/core/java/android/os/FileBridge.java
+++ b/core/java/android/os/FileBridge.java
@@ -31,7 +31,6 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.SyncFailedException;
 import java.nio.ByteOrder;
 import java.util.Arrays;
 
@@ -144,8 +143,7 @@
             }
         }
 
-        @Override
-        public void flush() throws IOException {
+        public void fsync() throws IOException {
             writeCommandAndBlock(CMD_FSYNC, "fsync()");
         }
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index d997e44..207dc4a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -336,26 +336,6 @@
     void removeVpnUidRanges(int netId, in UidRange[] ranges);
 
     /**
-     * Get the SO_MARK associated with routing packets for user {@code uid}
-     */
-    int getMarkForUid(int uid);
-
-    /**
-     * Get the SO_MARK associated with protecting packets from VPN routing rules
-     */
-    int getMarkForProtect();
-
-    /**
-     * Route all traffic in {@code route} to {@code iface} setup for marked forwarding
-     */
-    void setMarkedForwardingRoute(String iface, in RouteInfo route);
-
-    /**
-     * Clear routes set by {@link setMarkedForwardingRoute}
-     */
-    void clearMarkedForwardingRoute(String iface, in RouteInfo route);
-
-    /**
      * Exempts {@code host} from the routing set up by {@link setMarkedForwardingRoute}
      * All connects to {@code host} will use the global routing table
      */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a54320f..707d31a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -15,6 +15,7 @@
  */
 package android.os;
 
+import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -900,6 +901,8 @@
     public static int getMaxSupportedUsers() {
         // Don't allow multiple users on certain builds
         if (android.os.Build.ID.startsWith("JVP")) return 1;
+        // Svelte devices don't get multi-user.
+        if (ActivityManager.isLowRamDeviceStatic()) return 1;
         return SystemProperties.getInt("fw.max_users",
                 Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
     }
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 76ef544..46be928 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -36,10 +36,11 @@
  * @attr ref android.R.styleable#SwitchPreference_disableDependentsState
  */
 public class SwitchPreference extends TwoStatePreference {
+    private final Listener mListener = new Listener();
+
     // Switch text for on and off states
     private CharSequence mSwitchOn;
     private CharSequence mSwitchOff;
-    private final Listener mListener = new Listener();
 
     private class Listener implements CompoundButton.OnCheckedChangeListener {
         @Override
@@ -122,6 +123,11 @@
 
         View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
         if (checkableView != null && checkableView instanceof Checkable) {
+            if (checkableView instanceof Switch) {
+                final Switch switchView = (Switch) checkableView;
+                switchView.setOnCheckedChangeListener(null);
+            }
+
             ((Checkable) checkableView).setChecked(mChecked);
 
             sendAccessibilityEvent(checkableView);
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 8c229c5..8bc157a 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -87,7 +87,7 @@
      * @hide
      */
     public boolean contains(int pageIndex) {
-        return pageIndex >= mStart && pageIndex <= mEnd;
+        return (pageIndex >= mStart) && (pageIndex <= mEnd);
     }
 
     /**
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 7ec838e..9361286 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -105,7 +105,7 @@
 
     private static final String LOG_TAG = "PrintManager";
 
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
 
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 760f2a5..c6b6e20 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -408,7 +408,7 @@
                 values.put(CACHED_NUMBER_LABEL, ci.numberLabel);
             }
 
-            if ((ci != null) && (ci.person_id > 0)) {
+            if ((ci != null) && (ci.contactIdOrZero > 0)) {
                 // Update usage information for the number associated with the contact ID.
                 // We need to use both the number and the ID for obtaining a data ID since other
                 // contacts may have the same number.
@@ -422,7 +422,8 @@
                     cursor = resolver.query(Phone.CONTENT_URI,
                             new String[] { Phone._ID },
                             Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
-                            new String[] { String.valueOf(ci.person_id), normalizedPhoneNumber},
+                            new String[] { String.valueOf(ci.contactIdOrZero),
+                                    normalizedPhoneNumber},
                             null);
                 } else {
                     final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number;
@@ -431,7 +432,7 @@
                                     Uri.encode(phoneNumber)),
                             new String[] { Phone._ID },
                             Phone.CONTACT_ID + " =?",
-                            new String[] { String.valueOf(ci.person_id) },
+                            new String[] { String.valueOf(ci.contactIdOrZero) },
                             null);
                 }
 
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 2e9077a..e3e328c 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -108,6 +108,16 @@
         }
 
         @Override
+        public IVoiceInteractorRequest startCompleteVoice(String callingPackage,
+                IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
+            Request request = newRequest(callback);
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMPLETE_VOICE,
+                    new Caller(callingPackage, Binder.getCallingUid()), request,
+                    message, extras));
+            return request.mInterface;
+        }
+
+        @Override
         public IVoiceInteractorRequest startAbortVoice(String callingPackage,
                 IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
             Request request = newRequest(callback);
@@ -208,6 +218,16 @@
             }
         }
 
+        public void sendCompleteVoiceResult(Bundle result) {
+            try {
+                if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface
+                        + " result=" + result);
+                finishRequest();
+                mCallback.deliverCompleteVoiceResult(mInterface, result);
+            } catch (RemoteException e) {
+            }
+        }
+
         public void sendAbortVoiceResult(Bundle result) {
             try {
                 if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
@@ -249,10 +269,11 @@
     }
 
     static final int MSG_START_CONFIRMATION = 1;
-    static final int MSG_START_ABORT_VOICE = 2;
-    static final int MSG_START_COMMAND = 3;
-    static final int MSG_SUPPORTS_COMMANDS = 4;
-    static final int MSG_CANCEL = 5;
+    static final int MSG_START_COMPLETE_VOICE = 2;
+    static final int MSG_START_ABORT_VOICE = 3;
+    static final int MSG_START_COMMAND = 4;
+    static final int MSG_SUPPORTS_COMMANDS = 5;
+    static final int MSG_CANCEL = 6;
 
     static final int MSG_TASK_STARTED = 100;
     static final int MSG_TASK_FINISHED = 101;
@@ -271,6 +292,13 @@
                     onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
                             (Bundle)args.arg4);
                     break;
+                case MSG_START_COMPLETE_VOICE:
+                    args = (SomeArgs)msg.obj;
+                    if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((Request) args.arg2).mInterface
+                            + " message=" + args.arg3 + " extras=" + args.arg4);
+                    onCompleteVoice((Caller) args.arg1, (Request) args.arg2,
+                            (CharSequence) args.arg3, (Bundle) args.arg4);
+                    break;
                 case MSG_START_ABORT_VOICE:
                     args = (SomeArgs)msg.obj;
                     if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface
@@ -701,6 +729,27 @@
             Bundle extras);
 
     /**
+     * Request to complete the voice interaction session because the voice activity successfully
+     * completed its interaction using voice.  Corresponds to
+     * {@link android.app.VoiceInteractor.CompleteVoiceRequest
+     * VoiceInteractor.CompleteVoiceRequest}.  The default implementation just sends an empty
+     * confirmation back to allow the activity to exit.
+     *
+     * @param caller Who is making the request.
+     * @param request The active request.
+     * @param message The message informing the user of the problem, as per
+     * {@link android.app.VoiceInteractor.CompleteVoiceRequest
+     * VoiceInteractor.CompleteVoiceRequest}.
+     * @param extras Any additional information, as per
+     * {@link android.app.VoiceInteractor.CompleteVoiceRequest
+     * VoiceInteractor.CompleteVoiceRequest}.
+     */
+    public void onCompleteVoice(Caller caller, Request request, CharSequence message,
+           Bundle extras) {
+        request.sendCompleteVoiceResult(null);
+    }
+
+    /**
      * Request to abort the voice interaction session because the voice activity can not
      * complete its interaction using voice.  Corresponds to
      * {@link android.app.VoiceInteractor.AbortVoiceRequest
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
deleted file mode 100644
index 2f7945d..0000000
--- a/core/java/android/transition/MoveImage.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.transition;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-/**
- * TO BE REMOVED.
- * Use ChangeImageTransform + ChangeBounds instead.
- * @hide
- */
-public class MoveImage extends TransitionSet {
-
-    public MoveImage() {
-        init();
-    }
-
-    public MoveImage(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init();
-    }
-
-    private void init() {
-        addTransition(new ChangeBounds());
-        addTransition(new ChangeClipBounds());
-        addTransition(new ChangeTransform());
-        addTransition(new ChangeImageTransform());
-    }
-}
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index fd3e450..68b0a43 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -149,8 +149,6 @@
                 transition = new Slide(mContext, attrs);
             } else if ("explode".equals(name)) {
                 transition = new Explode(mContext, attrs);
-            } else if ("moveImage".equals(name)) {
-                transition = new MoveImage(mContext, attrs);
             } else if ("changeImageTransform".equals(name)) {
                 transition = new ChangeImageTransform(mContext, attrs);
             } else if ("changeTransform".equals(name)) {
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
new file mode 100644
index 0000000..6aae84d
--- /dev/null
+++ b/core/java/android/util/ExceptionUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import java.io.IOException;
+
+/**
+ * Utility methods for proxying richer exceptions across Binder calls.
+ *
+ * @hide
+ */
+public class ExceptionUtils {
+    // TODO: longer term these should be replaced with first-class
+    // Parcel.read/writeException() and AIDL support, but for now do this using
+    // a nasty hack.
+
+    private static final String PREFIX_IO = "\u2603";
+
+    public static RuntimeException wrap(IOException e) {
+        throw new IllegalStateException(PREFIX_IO + e.getMessage());
+    }
+
+    public static void maybeUnwrapIOException(RuntimeException e) throws IOException {
+        if ((e instanceof IllegalStateException) && e.getMessage().startsWith(PREFIX_IO)) {
+            throw new IOException(e.getMessage().substring(PREFIX_IO.length()));
+        }
+    }
+}
diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java
index 3907e77..211d01a 100644
--- a/core/java/android/util/Range.java
+++ b/core/java/android/util/Range.java
@@ -99,16 +99,16 @@
     /**
      * Checks if the {@code value} is within the bounds of this range.
      *
-     * <p>A value is considered to be within this range if it's {@code >=} then
-     * the lower endpoint <i>and</i> {@code <=} to the upper endpoint (using the {@link Comparable}
-     * interface.</p>
+     * <p>A value is considered to be within this range if it's {@code >=}
+     * the lower endpoint <i>and</i> {@code <=} the upper endpoint (using the {@link Comparable}
+     * interface.)</p>
      *
      * @param value a non-{@code null} {@code T} reference
      * @return {@code true} if the value is within this inclusive range, {@code false} otherwise
      *
      * @throws NullPointerException if {@code value} was {@code null}
      */
-    public boolean inRange(T value) {
+    public boolean contains(T value) {
         checkNotNull(value, "value must not be null");
 
         boolean gteLower = value.compareTo(mLower) >= 0;
@@ -118,6 +118,26 @@
     }
 
     /**
+     * Checks if another {@code range} is within the bounds of this range.
+     *
+     * <p>A range is considered to be within this range if both of its endpoints
+     * are within this range.</p>
+     *
+     * @param range a non-{@code null} {@code T} reference
+     * @return {@code true} if the range is within this inclusive range, {@code false} otherwise
+     *
+     * @throws NullPointerException if {@code range} was {@code null}
+     */
+    public boolean contains(Range<T> range) {
+        checkNotNull(range, "value must not be null");
+
+        boolean gteLower = range.mLower.compareTo(mLower) >= 0;
+        boolean lteUpper = range.mUpper.compareTo(mUpper) <= 0;
+
+        return gteLower && lteUpper;
+    }
+
+    /**
      * Compare two ranges for equality.
      *
      * <p>A range is considered equal if and only if both the lower and upper endpoints
@@ -140,6 +160,182 @@
     }
 
     /**
+     * Clamps {@code value} to this range.
+     *
+     * <p>If the value is within this range, it is returned.  Otherwise, if it
+     * is {@code <} than the lower endpoint, the lower endpoint is returned,
+     * else the upper endpoint is returned. Comparisons are performed using the
+     * {@link Comparable} interface.</p>
+     *
+     * @param value a non-{@code null} {@code T} reference
+     * @return {@code value} clamped to this range.
+     */
+    public T clamp(T value) {
+        checkNotNull(value, "value must not be null");
+
+        if (value.compareTo(mLower) < 0) {
+            return mLower;
+        } else if (value.compareTo(mUpper) > 0) {
+            return mUpper;
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * Returns the intersection of this range and another {@code range}.
+     * <p>
+     * E.g. if a {@code <} b {@code <} c {@code <} d, the
+     * intersection of [a, c] and [b, d] ranges is [b, c].
+     * As the endpoints are object references, there is no guarantee
+     * which specific endpoint reference is used from the input ranges:</p>
+     * <p>
+     * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
+     * intersection of [a, b] and [a', c] ranges could be either
+     * [a, b] or ['a, b], where [a, b] could be either the exact
+     * input range, or a newly created range with the same endpoints.</p>
+     *
+     * @param range a non-{@code null} {@code Range<T>} reference
+     * @return the intersection of this range and the other range.
+     *
+     * @throws NullPointerException if {@code range} was {@code null}
+     * @throws IllegalArgumentException if the ranges are disjoint.
+     */
+    public Range<T> intersect(Range<T> range) {
+        checkNotNull(range, "range must not be null");
+
+        int cmpLower = range.mLower.compareTo(mLower);
+        int cmpUpper = range.mUpper.compareTo(mUpper);
+
+        if (cmpLower <= 0 && cmpUpper >= 0) {
+            // range includes this
+            return this;
+        } else if (cmpLower >= 0 && cmpUpper <= 0) {
+            // this inludes range
+            return range;
+        } else {
+            return Range.create(
+                    cmpLower <= 0 ? mLower : range.mLower,
+                    cmpUpper >= 0 ? mUpper : range.mUpper);
+        }
+    }
+
+    /**
+     * Returns the intersection of this range and the inclusive range
+     * specified by {@code [lower, upper]}.
+     * <p>
+     * See {@link #intersect(Range)} for more details.</p>
+     *
+     * @param lower a non-{@code null} {@code T} reference
+     * @param upper a non-{@code null} {@code T} reference
+     * @return the intersection of this range and the other range
+     *
+     * @throws NullPointerException if {@code lower} or {@code upper} was {@code null}
+     * @throws IllegalArgumentException if the ranges are disjoint.
+     */
+    public Range<T> intersect(T lower, T upper) {
+        checkNotNull(lower, "lower must not be null");
+        checkNotNull(upper, "upper must not be null");
+
+        int cmpLower = lower.compareTo(mLower);
+        int cmpUpper = upper.compareTo(mUpper);
+
+        if (cmpLower <= 0 && cmpUpper >= 0) {
+            // [lower, upper] includes this
+            return this;
+        } else {
+            return Range.create(
+                    cmpLower <= 0 ? mLower : lower,
+                    cmpUpper >= 0 ? mUpper : upper);
+        }
+    }
+
+    /**
+     * Returns the smallest range that includes this range and
+     * another {@code range}.
+     * <p>
+     * E.g. if a {@code <} b {@code <} c {@code <} d, the
+     * extension of [a, c] and [b, d] ranges is [a, d].
+     * As the endpoints are object references, there is no guarantee
+     * which specific endpoint reference is used from the input ranges:</p>
+     * <p>
+     * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
+     * extension of [a, b] and [a', c] ranges could be either
+     * [a, c] or ['a, c], where ['a, c] could be either the exact
+     * input range, or a newly created range with the same endpoints.</p>
+     *
+     * @param range a non-{@code null} {@code Range<T>} reference
+     * @return the extension of this range and the other range.
+     *
+     * @throws NullPointerException if {@code range} was {@code null}
+     */
+    public Range<T> extend(Range<T> range) {
+        checkNotNull(range, "range must not be null");
+
+        int cmpLower = range.mLower.compareTo(mLower);
+        int cmpUpper = range.mUpper.compareTo(mUpper);
+
+        if (cmpLower <= 0 && cmpUpper >= 0) {
+            // other includes this
+            return range;
+        } else if (cmpLower >= 0 && cmpUpper <= 0) {
+            // this inludes other
+            return this;
+        } else {
+            return Range.create(
+                    cmpLower >= 0 ? mLower : range.mLower,
+                    cmpUpper <= 0 ? mUpper : range.mUpper);
+        }
+    }
+
+    /**
+     * Returns the smallest range that includes this range and
+     * the inclusive range specified by {@code [lower, upper]}.
+     * <p>
+     * See {@link #extend(Range)} for more details.</p>
+     *
+     * @param lower a non-{@code null} {@code T} reference
+     * @param upper a non-{@code null} {@code T} reference
+     * @return the extension of this range and the other range.
+     *
+     * @throws NullPointerException if {@code lower} or {@code
+     *                              upper} was {@code null}
+     */
+    public Range<T> extend(T lower, T upper) {
+        checkNotNull(lower, "lower must not be null");
+        checkNotNull(upper, "upper must not be null");
+
+        int cmpLower = lower.compareTo(mLower);
+        int cmpUpper = upper.compareTo(mUpper);
+
+        if (cmpLower >= 0 && cmpUpper <= 0) {
+            // this inludes other
+            return this;
+        } else {
+            return Range.create(
+                    cmpLower >= 0 ? mLower : lower,
+                    cmpUpper <= 0 ? mUpper : upper);
+        }
+    }
+
+    /**
+     * Returns the smallest range that includes this range and
+     * the {@code value}.
+     * <p>
+     * See {@link #extend(Range)} for more details, as this method is
+     * equivalent to {@code extend(Range.create(value, value))}.</p>
+     *
+     * @param value a non-{@code null} {@code T} reference
+     * @return the extension of this range and the value.
+     *
+     * @throws NullPointerException if {@code value} was {@code null}
+     */
+    public Range<T> extend(T value) {
+        checkNotNull(value, "value must not be null");
+        return extend(value, value);
+    }
+
+    /**
      * Return the range as a string representation {@code "[lower, upper]"}.
      *
      * @return string representation of the range
diff --git a/core/java/android/util/Rational.java b/core/java/android/util/Rational.java
index 9952859..80d26d9 100644
--- a/core/java/android/util/Rational.java
+++ b/core/java/android/util/Rational.java
@@ -536,8 +536,67 @@
         } else { // finite value
             if (gcd(mNumerator, mDenominator) > 1) {
                 throw new InvalidObjectException(
-                    "Rational must be deserialized from a reduced form for finite values");
+                        "Rational must be deserialized from a reduced form for finite values");
             }
         }
     }
+
+    private static NumberFormatException invalidRational(String s) {
+        throw new NumberFormatException("Invalid Rational: \"" + s + "\"");
+    }
+
+    /**
+     * Parses the specified string as a rational value.
+     * <p>The ASCII characters {@code \}{@code u003a} (':') and
+     * {@code \}{@code u002f} ('/') are recognized as separators between
+     * the numerator and denumerator.</p>
+     * <p>
+     * For any {@code Rational r}: {@code Rational.parseRational(r.toString()).equals(r)}.
+     * However, the method also handles rational numbers expressed in the
+     * following forms:</p>
+     * <p>
+     * "<i>num</i>{@code /}<i>den</i>" or
+     * "<i>num</i>{@code :}<i>den</i>" {@code => new Rational(num, den);},
+     * where <i>num</i> and <i>den</i> are string integers potentially
+     * containing a sign, such as "-10", "+7" or "5".</p>
+     *
+     * <pre>{@code
+     * Rational.parseRational("3:+6").equals(new Rational(1, 2)) == true
+     * Rational.parseRational("-3/-6").equals(new Rational(1, 2)) == true
+     * Rational.parseRational("4.56") => throws NumberFormatException
+     * }</pre>
+     *
+     * @param string the string representation of a rational value.
+     * @return the rational value represented by {@code string}.
+     *
+     * @throws NumberFormatException if {@code string} cannot be parsed
+     * as a rational value.
+     * @throws NullPointerException if {@code string} was {@code null}
+     */
+    public static Rational parseRational(String string)
+            throws NumberFormatException {
+        checkNotNull(string, "string must not be null");
+
+        if (string.equals("NaN")) {
+            return NaN;
+        } else if (string.equals("Infinity")) {
+            return POSITIVE_INFINITY;
+        } else if (string.equals("-Infinity")) {
+            return NEGATIVE_INFINITY;
+        }
+
+        int sep_ix = string.indexOf(':');
+        if (sep_ix < 0) {
+            sep_ix = string.indexOf('/');
+        }
+        if (sep_ix < 0) {
+            throw invalidRational(string);
+        }
+        try {
+            return new Rational(Integer.parseInt(string.substring(0, sep_ix)),
+                    Integer.parseInt(string.substring(sep_ix + 1)));
+        } catch (NumberFormatException e) {
+            throw invalidRational(string);
+        }
+    }
 }
diff --git a/core/java/android/util/Size.java b/core/java/android/util/Size.java
index ba1a35f..d58f778 100644
--- a/core/java/android/util/Size.java
+++ b/core/java/android/util/Size.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import static com.android.internal.util.Preconditions.*;
+
 /**
  * Immutable class for describing width and height dimensions in pixels.
  */
@@ -84,6 +86,58 @@
         return mWidth + "x" + mHeight;
     }
 
+    private static NumberFormatException invalidSize(String s) {
+        throw new NumberFormatException("Invalid Size: \"" + s + "\"");
+    }
+
+    /**
+     * Parses the specified string as a size value.
+     * <p>
+     * The ASCII characters {@code \}{@code u002a} ('*') and
+     * {@code \}{@code u0078} ('x') are recognized as separators between
+     * the width and height.</p>
+     * <p>
+     * For any {@code Size s}: {@code Size.parseSize(s.toString()).equals(s)}.
+     * However, the method also handles sizes expressed in the
+     * following forms:</p>
+     * <p>
+     * "<i>width</i>{@code x}<i>height</i>" or
+     * "<i>width</i>{@code *}<i>height</i>" {@code => new Size(width, height)},
+     * where <i>width</i> and <i>height</i> are string integers potentially
+     * containing a sign, such as "-10", "+7" or "5".</p>
+     *
+     * <pre>{@code
+     * Size.parseSize("3*+6").equals(new Size(3, 6)) == true
+     * Size.parseSize("-3x-6").equals(new Size(-3, -6)) == true
+     * Size.parseSize("4 by 3") => throws NumberFormatException
+     * }</pre>
+     *
+     * @param string the string representation of a size value.
+     * @return the size value represented by {@code string}.
+     *
+     * @throws NumberFormatException if {@code string} cannot be parsed
+     * as a size value.
+     * @throws NullPointerException if {@code string} was {@code null}
+     */
+    public static Size parseSize(String string)
+            throws NumberFormatException {
+        checkNotNull(string, "string must not be null");
+
+        int sep_ix = string.indexOf('*');
+        if (sep_ix < 0) {
+            sep_ix = string.indexOf('x');
+        }
+        if (sep_ix < 0) {
+            throw invalidSize(string);
+        }
+        try {
+            return new Size(Integer.parseInt(string.substring(0, sep_ix)),
+                    Integer.parseInt(string.substring(sep_ix + 1)));
+        } catch (NumberFormatException e) {
+            throw invalidSize(string);
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index be677ea..f2f363a 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -248,24 +248,8 @@
     abstract void detachSurfaceTexture(long hardwareLayer);
 
     /**
-     * Setup the hardware renderer for drawing. This is called whenever the size
-     * of the target surface changes or when the surface is first created.
-     *
-     * @param width Width of the drawing surface.
-     * @param height Height of the drawing surface.
-     * @param surfaceInsets Insets between the drawing surface and actual
-     *            surface bounds.
-     * @param lightX X position of the shadow casting light
-     * @param lightY Y position of the shadow casting light
-     * @param lightZ Z position of the shadow casting light
-     * @param lightRadius radius of the shadow casting light
-     */
-    abstract void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY,
-            float lightZ, float lightRadius);
-
-    /**
      * Gets the current width of the surface. This is the width that the surface
-     * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
+     * was last set to in a call to {@link #setup(int, int, Rect)}.
      *
      * @return the current width of the surface
      */
@@ -273,7 +257,7 @@
 
     /**
      * Gets the current height of the surface. This is the height that the surface
-     * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
+     * was last set to in a call to {@link #setup(int, int, Rect)}.
      *
      * @return the current width of the surface
      */
@@ -371,19 +355,18 @@
      * @param width The width of the drawing surface.
      * @param height The height of the drawing surface.
      * @param surface The surface to hardware accelerate
-     * @param metrics The display metrics used to draw the output.
      * @param surfaceInsets The drawing surface insets to apply
      *
      * @return true if the surface was initialized, false otherwise. Returning
      *         false might mean that the surface was already initialized.
      */
-    boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets, DisplayMetrics metrics)
+    boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets)
             throws OutOfResourcesException {
         if (isRequested()) {
             // We lost the gl context, so recreate it.
             if (!isEnabled()) {
                 if (initialize(surface)) {
-                    setup(width, height, surfaceInsets, metrics);
+                    setup(width, height, surfaceInsets);
                     return true;
                 }
             }
@@ -391,13 +374,14 @@
         return false;
     }
 
-    void setup(int width, int height, Rect surfaceInsets, DisplayMetrics metrics) {
-        float lightX = width / 2.0f;
-        float lightY = -400 * metrics.density;
-        float lightZ = 800 * metrics.density;
-        float lightRadius = 800 * metrics.density;
-        setup(width, height, surfaceInsets, lightX, lightY, lightZ, lightRadius);
-    }
+    /**
+     * Sets up the renderer for drawing.
+     *
+     * @param width The width of the drawing surface.
+     * @param height The height of the drawing surface.
+     * @param surfaceInsets The drawing surface insets to apply
+     */
+    abstract void setup(int width, int height, Rect surfaceInsets);
 
     /**
      * Optional, sets the name of the renderer. Useful for debugging purposes.
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index acb2fe4..fb8ce15 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -16,8 +16,11 @@
 
 package android.view;
 
+import com.android.internal.R;
+
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -78,6 +81,13 @@
     // applied as translation when updating the root render node.
     private int mInsetTop, mInsetLeft;
 
+    // Light and shadow properties specified by the theme.
+    private final float mLightY;
+    private final float mLightZ;
+    private final float mLightRadius;
+    private final float mAmbientShadowAlpha;
+    private final float mSpotShadowAlpha;
+
     private long mNativeProxy;
     private boolean mInitialized = false;
     private RenderNode mRootNode;
@@ -85,6 +95,15 @@
     private boolean mProfilingEnabled;
 
     ThreadedRenderer(Context context, boolean translucent) {
+        final TypedArray a = context.obtainStyledAttributes(
+                null, R.styleable.Lighting, R.attr.lightingStyle, 0);
+        mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
+        mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
+        mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
+        mAmbientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
+        mSpotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
+        a.recycle();
+
         long rootNodePtr = nCreateRootRenderNode();
         mRootNode = RenderNode.adopt(rootNodePtr);
         mRootNode.setClipToBounds(false);
@@ -164,8 +183,8 @@
     }
 
     @Override
-    void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, float lightZ,
-            float lightRadius) {
+    void setup(int width, int height, Rect surfaceInsets) {
+        final float lightX = width / 2.0f;
         mWidth = width;
         mHeight = height;
         if (surfaceInsets != null) {
@@ -180,7 +199,7 @@
             mSurfaceHeight = height;
         }
         mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
-        nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, lightY, lightZ, lightRadius);
+        nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, mLightY, mLightZ, mLightRadius);
     }
 
     @Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ec39ee7..a09a061 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2374,30 +2374,27 @@
     static final int PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT = 0x8;
 
     /**
-     * Flag indicating that an overridden method correctly  called down to
+     * Flag indicating that an overridden method correctly called down to
      * the superclass implementation as required by the API spec.
      */
     static final int PFLAG3_CALLED_SUPER = 0x10;
 
-    @Deprecated
-    static final int PFLAG3_OUTLINE_DEFINED = 0x20;
-
     /**
      * Flag indicating that we're in the process of applying window insets.
      */
-    static final int PFLAG3_APPLYING_INSETS = 0x40;
+    static final int PFLAG3_APPLYING_INSETS = 0x20;
 
     /**
      * Flag indicating that we're in the process of fitting system windows using the old method.
      */
-    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
+    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x40;
 
     /**
      * Flag indicating that nested scrolling is enabled for this view.
      * The view will optionally cooperate with views up its parent chain to allow for
      * integrated nested scrolling along the same axis.
      */
-    static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x200;
+    static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80;
 
     /* End of masks for mPrivateFlags3 */
 
@@ -3273,8 +3270,6 @@
 
     private int[] mDrawableState = null;
 
-    @Deprecated
-    private Outline mOutline;
     ViewOutlineProvider mOutlineProvider = ViewOutlineProvider.BACKGROUND;
 
     /**
@@ -10751,23 +10746,9 @@
         }
     }
 
+    /** Deprecated, pending removal */
     @Deprecated
-    public void setOutline(@Nullable Outline outline) {
-        mPrivateFlags3 |= PFLAG3_OUTLINE_DEFINED;
-
-        if (outline == null || outline.isEmpty()) {
-            if (mOutline != null) {
-                mOutline.setEmpty();
-            }
-        } else {
-            // always copy the path since caller may reuse
-            if (mOutline == null) {
-                mOutline = new Outline();
-            }
-            mOutline.set(outline);
-        }
-        mRenderNode.setOutline(mOutline);
-    }
+    public void setOutline(@Nullable Outline outline) {}
 
     /**
      * Returns whether the Outline should be used to clip the contents of the View.
@@ -10836,12 +10817,6 @@
      * @see #setOutlineProvider(ViewOutlineProvider)
      */
     public void invalidateOutline() {
-        if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) != 0) {
-            // TODO: remove this when removing old outline code
-            // setOutline() was called to manually set outline, ignore provider
-            return;
-        }
-
         // Unattached views ignore this signal, and outline is recomputed in onAttachedToWindow()
         if (mAttachInfo == null) return;
 
@@ -14948,6 +14923,7 @@
             matrix.setScale(1, fadeHeight * topFadeStrength);
             matrix.postTranslate(left, top);
             fade.setLocalMatrix(matrix);
+            p.setShader(fade);
             canvas.drawRect(left, top, right, top + length, p);
         }
 
@@ -14956,6 +14932,7 @@
             matrix.postRotate(180);
             matrix.postTranslate(left, bottom);
             fade.setLocalMatrix(matrix);
+            p.setShader(fade);
             canvas.drawRect(left, bottom - length, right, bottom, p);
         }
 
@@ -14964,6 +14941,7 @@
             matrix.postRotate(-90);
             matrix.postTranslate(left, top);
             fade.setLocalMatrix(matrix);
+            p.setShader(fade);
             canvas.drawRect(left, top, left + length, bottom, p);
         }
 
@@ -14972,6 +14950,7 @@
             matrix.postRotate(90);
             matrix.postTranslate(right, top);
             fade.setLocalMatrix(matrix);
+            p.setShader(fade);
             canvas.drawRect(right - length, top, right, bottom, p);
         }
 
@@ -17493,7 +17472,8 @@
                 getLocationInWindow(location);
                 region.op(location[0], location[1], location[0] + mRight - mLeft,
                         location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
-            } else if ((pflags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) {
+            } else if ((pflags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null &&
+                    mBackground.getOpacity() != PixelFormat.TRANSPARENT) {
                 // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
                 // exists, so we remove the background drawable's non-transparent
                 // parts from this transparent region.
@@ -19388,14 +19368,6 @@
     }
 
     /**
-     * To be removed before L release.
-     * @hide
-     */
-    public final void setViewName(String transitionName) {
-        setTransitionName(transitionName);
-    }
-
-    /**
      * Returns the name of the View to be used to identify Views in Transitions.
      * Names should be unique in the View hierarchy.
      *
@@ -19409,12 +19381,6 @@
     }
 
     /**
-     * To be removed before L release.
-     * @hide
-     */
-    public String getViewName() { return getTransitionName(); }
-
-    /**
      * Interface definition for a callback to be invoked when a hardware key event is
      * dispatched to this view. The callback will be invoked before the key event is
      * given to the view. This is only useful for hardware keyboards; a software input
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index edc9971..ac70066 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3333,6 +3333,17 @@
     }
 
     /**
+     * Check if this ViewGroup is configured to clip child views to its padding.
+     *
+     * @return true if this ViewGroup clips children to its padding, false otherwise
+     *
+     * @attr ref android.R.styleable#ViewGroup_clipToPadding
+     */
+    public boolean getClipToPadding() {
+        return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9405299..7fab808 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1714,8 +1714,7 @@
                         mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
                         mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
                     final Rect shadowInsets = params != null ? params.shadowInsets : null;
-                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets,
-                            mAttachInfo.mRootView.getResources().getDisplayMetrics());
+                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets);
                     if (!hwInitialized) {
                         mAttachInfo.mHardwareRenderer.invalidate(mSurface);
                         mFullRedrawNeeded = true;
@@ -2459,8 +2458,7 @@
 
                     try {
                         attachInfo.mHardwareRenderer.initializeIfNeeded(
-                                mWidth, mHeight, mSurface, surfaceInsets,
-                                attachInfo.mRootView.getResources().getDisplayMetrics());
+                                mWidth, mHeight, mSurface, surfaceInsets);
                     } catch (OutOfResourcesException e) {
                         handleOutOfResourcesException(e);
                         return;
@@ -3159,8 +3157,7 @@
                                 final WindowManager.LayoutParams lp = mWindowAttributes;
                                 final Rect surfaceInsets = lp != null ? lp.shadowInsets : null;
                                 mAttachInfo.mHardwareRenderer.initializeIfNeeded(
-                                        mWidth, mHeight, mSurface, surfaceInsets,
-                                        mAttachInfo.mRootView.getResources().getDisplayMetrics());
+                                        mWidth, mHeight, mSurface, surfaceInsets);
                             } catch (OutOfResourcesException e) {
                                 Log.e(TAG, "OutOfResourcesException locking surface", e);
                                 try {
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index e1f40b7..338f3d2 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -429,6 +429,21 @@
     }
 
     /**
+     * The default implementation is responsible for handling
+     * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}. In fact, for derived classes, calling
+     * {@code super.requestCursorAnchorInfo(request)} is the only way to handle
+     * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}.
+     */
+    public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+        if (request != null && mIMM != null &&
+                request.getRequestType() == CursorAnchorInfoRequest.TYPE_CURSOR_RECT) {
+            mIMM.setCursorRectMonitorMode(request.getRequestFlags());
+            return CursorAnchorInfoRequest.RESULT_SCHEDULED;
+        }
+        return CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
+    }
+
+    /**
      * The default implementation places the given text into the editable,
      * replacing any existing composing text.  The new text is marked as
      * in a composing state with the composing style.
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index 04d875d..730b7f6 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -257,11 +257,12 @@
         /**
          * Sets the text range of the composing text. Calling this can be skipped if there is
          * no composing text.
-         * @param index index where the composing text starts.
+         * @param composingTextStart index where the composing text starts.
          * @param composingText the entire composing text.
          */
-        public Builder setComposingText(final int index, final CharSequence composingText) {
-            mComposingTextStart = index;
+        public Builder setComposingText(final int composingTextStart,
+            final CharSequence composingText) {
+            mComposingTextStart = composingTextStart;
             if (composingText == null) {
                 mComposingText = null;
             } else {
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl
similarity index 89%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl
index 3da1321..41ef7cc6 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.bluetooth.le;
+package android.view.inputmethod;
 
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable CursorAnchorInfoRequest;
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java
new file mode 100644
index 0000000..e4c94f2
--- /dev/null
+++ b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+/**
+ * Used to enable or disable event notification for
+ * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}. This class is also used to
+ * enable {@link InputMethodService#onUpdateCursor(android.graphics.Rect)} for existing editors
+ * that have not supported {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}.
+ */
+public final class CursorAnchorInfoRequest implements Parcelable {
+    private final int mRequestType;
+    private final int mRequestFlags;
+
+    /**
+     * Not handled by the editor.
+     */
+    public static final int RESULT_NOT_HANDLED = 0x00;
+    /**
+     * Request is scheduled in the editor task queue.
+     */
+    public static final int RESULT_SCHEDULED = 0x01;
+
+    /**
+     * The request is for {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}.
+     * This mechanism is powerful enough to retrieve fine-grained positional information of
+     * characters in the editor.
+     */
+    public static final int TYPE_CURSOR_ANCHOR_INFO = 0x01;
+    /**
+     * The editor is requested to call
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+     * whenever cursor/anchor position is changed. To disable monitoring, call
+     * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with
+     * {@link #TYPE_CURSOR_ANCHOR_INFO} and this flag off.
+     * <p>
+     * This flag can be used together with {@link #FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE}.
+     * </p>
+     */
+    public static final int FLAG_CURSOR_ANCHOR_INFO_MONITOR = 0x01;
+    /**
+     * The editor is requested to call
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
+     * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
+     * used together with {@link #FLAG_CURSOR_ANCHOR_INFO_MONITOR}.
+     */
+    public static final int FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE = 0x02;
+
+    /**
+     * The request is for {@link InputMethodService#onUpdateCursor(android.graphics.Rect)}. This
+     * mechanism has been available since API Level 3 (CUPCAKE) but only the cursor rectangle can
+     * be retrieved with this mechanism.
+     */
+    public static final int TYPE_CURSOR_RECT = 0x02;
+    /**
+     * The editor is requested to call
+     * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)}
+     * whenever the cursor position is changed. To disable monitoring, call
+     * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with
+     * {@link #TYPE_CURSOR_RECT} and this flag off.
+     * <p>
+     * This flag can be used together with {@link #FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES}.
+     * </p>
+     */
+    public static final int FLAG_CURSOR_RECT_MONITOR = 0x01;
+    /**
+     * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)} should be
+     * called back in screen coordinates. To receive cursor position in local coordinates, call
+     * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with
+     * {@link #TYPE_CURSOR_RECT} and this flag off.
+     */
+    public static final int FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES = 0x02;
+    /**
+     * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)} should be
+     * called back in screen coordinates after coordinate conversion with {@link View#getMatrix()}.
+     * To disable coordinate conversion with {@link View#getMatrix()} again, call
+     * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} with
+     * {@link #TYPE_CURSOR_RECT} and this flag off.
+     *
+     * <p>
+     * The flag is ignored if {@link #FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES} is off.
+     * </p>
+     */
+    public static final int FLAG_CURSOR_RECT_WITH_VIEW_MATRIX = 0x04;
+
+    /**
+     * Constructs the object with request type and type-specific flags.
+     *
+     * @param requestType the type of this request. Currently {@link #TYPE_CURSOR_ANCHOR_INFO} or
+     * {@link #TYPE_CURSOR_RECT} is supported.
+     * @param requestFlags the flags for the given request type.
+     */
+    public CursorAnchorInfoRequest(int requestType, int requestFlags) {
+        mRequestType = requestType;
+        mRequestFlags = requestFlags;
+    }
+
+    /**
+     * Used to make this class parcelable.
+     *
+     * @param source the parcel from which the object is unmarshalled.
+     */
+    public CursorAnchorInfoRequest(Parcel source) {
+        mRequestType = source.readInt();
+        mRequestFlags = source.readInt();
+    }
+
+    /**
+     * @return the type of this request.
+     */
+    public int getRequestType() {
+        return mRequestType;
+    }
+
+    /**
+     * @return the flags that are specific to the type of this request.
+     */
+    public int getRequestFlags() {
+        return mRequestFlags;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRequestType);
+        dest.writeInt(mRequestFlags);
+    }
+
+    @Override
+    public int hashCode(){
+        return mRequestType * 31 + mRequestFlags;
+    }
+
+    @Override
+    public boolean equals(Object obj){
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof CursorAnchorInfoRequest)) {
+            return false;
+        }
+        final CursorAnchorInfoRequest that = (CursorAnchorInfoRequest) obj;
+        if (hashCode() != that.hashCode()) {
+            return false;
+        }
+        return mRequestType != that.mRequestType && mRequestFlags == that.mRequestFlags;
+    }
+
+    @Override
+    public String toString() {
+        return "CursorAnchorInfoRequest{mRequestType=" + mRequestType
+                + " mRequestFlags=" + mRequestFlags
+                + "}";
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<CursorAnchorInfoRequest> CREATOR =
+            new Parcelable.Creator<CursorAnchorInfoRequest>() {
+        @Override
+        public CursorAnchorInfoRequest createFromParcel(Parcel source) {
+            return new CursorAnchorInfoRequest(source);
+        }
+
+        @Override
+        public CursorAnchorInfoRequest[] newArray(int size) {
+            return new CursorAnchorInfoRequest[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 3537aec..dff91dc 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -723,4 +723,15 @@
      * valid.
      */
     public boolean performPrivateCommand(String action, Bundle data);
+
+    /**
+     * Called by the IME to ask the editor for calling back
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to
+     * notify cursor/anchor locations.
+     *
+     * @param request the details of the request.
+     * @return a result code that depends on {@link CursorAnchorInfoRequest#getRequestType()}. See
+     * {@link CursorAnchorInfoRequest} for details.
+     */
+    public int requestCursorAnchorInfo(CursorAnchorInfoRequest request);
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index a48473e..c831d7c 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -125,4 +125,8 @@
     public boolean performPrivateCommand(String action, Bundle data) {
         return mTarget.performPrivateCommand(action, data);
     }
-}
+
+    public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+        return mTarget.requestCursorAnchorInfo(request);
+    }
+ }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ace8808..623b5f9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -25,8 +25,9 @@
 import com.android.internal.view.InputBindResult;
 
 import android.content.Context;
+import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.inputmethodservice.InputMethodService;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -312,8 +313,9 @@
     CompletionInfo[] mCompletions;
     
     // Cursor position on the screen.
-    Rect mTmpCursorRect = new Rect();
+    Rect mNextCursorRect = new Rect();
     Rect mCursorRect = new Rect();
+    RectF mTempRectF = new RectF();
     int mCursorSelStart;
     int mCursorSelEnd;
     int mCursorCandStart;
@@ -348,8 +350,13 @@
      */
     private final int[] mViewTopLeft = new int[2];
 
+    /**
+     * The matrix to convert the view location into screen coordinates in {@link #updateCursor}.
+     */
+    private final Matrix mViewToScreenMatrix = new Matrix();
+
     // -----------------------------------------------------------
-    
+
     /**
      * Sequence number of this binding, as returned by the server.
      */
@@ -365,10 +372,28 @@
     InputChannel mCurChannel;
     ImeInputEventSender mCurSender;
 
+    private static final int CURSOR_RECT_MONITOR_MODE_NONE = 0x0;
+
+    private static final int CURSOR_RECT_MONITOR_FLAG_MASK =
+            CursorAnchorInfoRequest.FLAG_CURSOR_RECT_MONITOR |
+            CursorAnchorInfoRequest.FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES |
+            CursorAnchorInfoRequest.FLAG_CURSOR_RECT_WITH_VIEW_MATRIX;
+
+    private static final int CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE = 0x0;
+
+    private static final int CURSOR_ANCHOR_INFO_MONITOR_FLAG_MASK =
+            CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_MONITOR |
+            CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE;
+
     /**
-     * The current cursor/anchor monitor mode.
+     * The monitor mode for {@link #updateCursor(View, int, int, int, int)}.
      */
-    int mCursorAnchorMonitorMode = InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_NONE;
+    private int mCursorRectMonitorMode = CURSOR_RECT_MONITOR_MODE_NONE;
+
+    /**
+     * The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+     */
+    private int mCursorAnchorInfoMonitorMode = CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE;
 
     final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
     final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
@@ -382,7 +407,6 @@
     static final int MSG_SEND_INPUT_EVENT = 5;
     static final int MSG_TIMEOUT_INPUT_EVENT = 6;
     static final int MSG_FLUSH_INPUT_EVENT = 7;
-    static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 8;
     static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9;
 
     class H extends Handler {
@@ -422,6 +446,9 @@
                             return;
                         }
 
+                        mCursorAnchorInfoMonitorMode = CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE;
+                        mCursorRectMonitorMode = CURSOR_RECT_MONITOR_MODE_NONE;
+
                         setInputChannelLocked(res.channel);
                         mCurMethod = res.method;
                         mCurId = res.id;
@@ -514,15 +541,6 @@
                     finishedInputEvent(msg.arg1, false, false);
                     return;
                 }
-                case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE: {
-                    synchronized (mH) {
-                        mCursorAnchorMonitorMode = msg.arg1;
-                        // Clear the cache.
-                        mCursorRect.setEmpty();
-                        mCursorAnchorInfo = null;
-                    }
-                    return;
-                }
                 case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
                     synchronized (mH) {
                         mNextUserActionNotificationSequenceNumber = msg.arg1;
@@ -594,11 +612,6 @@
         }
 
         @Override
-        public void setCursorAnchorMonitorMode(int monitorMode) {
-            mH.sendMessage(mH.obtainMessage(MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0));
-        }
-
-        @Override
         public void setUserActionNotificationSequenceNumber(int sequenceNumber) {
             mH.sendMessage(mH.obtainMessage(MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
                     sequenceNumber, 0));
@@ -1535,37 +1548,45 @@
             return false;
         }
         synchronized (mH) {
-            return (mCursorAnchorMonitorMode &
-                    InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0;
+            return (mCursorRectMonitorMode & CursorAnchorInfoRequest.FLAG_CURSOR_RECT_MONITOR) != 0;
         }
     }
 
     /**
-     * Returns true if the current input method wants to receive the cursor rectangle in
-     * screen coordinates rather than local coordinates in the attached view.
+     * Updates the result of {@link #isWatchingCursor(View)}.
      *
      * @hide
      */
-    public boolean usesScreenCoordinatesForCursorLocked() {
-        // {@link InputMethodService#CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT} also means
-        // that {@link InputMethodService#onUpdateCursor} should provide the cursor rectangle
-        // in screen coordinates rather than local coordinates.
-        return (mCursorAnchorMonitorMode &
-                InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0;
+    public void setCursorRectMonitorMode(int flags) {
+        synchronized (mH) {
+            mCursorRectMonitorMode = (CURSOR_RECT_MONITOR_FLAG_MASK & flags);
+        }
     }
 
     /**
-     * Set cursor/anchor monitor mode via {@link com.android.server.InputMethodManagerService}.
-     * This is an internal method for {@link android.inputmethodservice.InputMethodService} and
-     * should never be used from IMEs and applications.
+     * Returns true if the current input method wants to be notified when cursor/anchor location
+     * is changed.
      *
      * @hide
      */
-    public void setCursorAnchorMonitorMode(IBinder imeToken, int monitorMode) {
-        try {
-            mService.setCursorAnchorMonitorMode(imeToken, monitorMode);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
+    public boolean isCursorAnchorInfoEnabled() {
+        synchronized (mH) {
+            final boolean isImmediate = (mCursorAnchorInfoMonitorMode &
+                    CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE) != 0;
+            final boolean isMonitoring = (mCursorAnchorInfoMonitorMode &
+                    CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_MONITOR) != 0;
+            return isImmediate || isMonitoring;
+        }
+    }
+
+    /**
+     * Updates the result of {@link #isWatchingCursor(View)}.
+     *
+     * @hide
+     */
+    public void setCursorAnchorInfoMonitorMode(int flags) {
+        synchronized (mH) {
+            mCursorAnchorInfoMonitorMode = (CURSOR_ANCHOR_INFO_MONITOR_FLAG_MASK & flags);
         }
     }
 
@@ -1581,16 +1602,32 @@
                 return;
             }
             if (DEBUG) Log.d(TAG, "updateCursor");
-            mTmpCursorRect.set(left, top, right, bottom);
-            if (!Objects.equals(mCursorRect, mTmpCursorRect)) {
+            final boolean usesScreenCoordinates = (mCursorRectMonitorMode &
+                    CursorAnchorInfoRequest.FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES) != 0;
+            if (usesScreenCoordinates) {
+                view.getLocationOnScreen(mViewTopLeft);
+                final Matrix viewMatrix = view.getMatrix();
+                final boolean usesViewMatrix = (viewMatrix != null) && ((mCursorRectMonitorMode &
+                        CursorAnchorInfoRequest.FLAG_CURSOR_RECT_WITH_VIEW_MATRIX) != 0);
+                if (usesViewMatrix) {
+                    mTempRectF.set(left, top, right, bottom);
+                    mViewToScreenMatrix.set(viewMatrix);
+                    mViewToScreenMatrix.postTranslate(mViewTopLeft[0], mViewTopLeft[1]);
+                    mViewToScreenMatrix.mapRect(mTempRectF);
+                    mNextCursorRect.set((int)mTempRectF.left, (int)mTempRectF.top,
+                            (int)mTempRectF.right, (int)mTempRectF.bottom);
+                } else {
+                    mNextCursorRect.set(left + mViewTopLeft[0], top + mViewTopLeft[1],
+                            right + mViewTopLeft[0], bottom + mViewTopLeft[1]);
+                }
+            } else {
+                mNextCursorRect.set(left, top, right, bottom);
+            }
+            if (!Objects.equals(mCursorRect, mNextCursorRect)) {
+                if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mNextCursorRect);
                 try {
-                    if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
-                    mCursorRect.set(mTmpCursorRect);
-                    if (usesScreenCoordinatesForCursorLocked()) {
-                        view.getLocationOnScreen(mViewTopLeft);
-                        mTmpCursorRect.offset(mViewTopLeft[0], mViewTopLeft[1]);
-                    }
-                    mCurMethod.updateCursor(mTmpCursorRect);
+                    mCurMethod.updateCursor(mNextCursorRect);
+                    mCursorRect.set(mNextCursorRect);
                 } catch (RemoteException e) {
                     Log.w(TAG, "IME died: " + mCurId, e);
                 }
@@ -1613,7 +1650,11 @@
                     || mCurrentTextBoxAttribute == null || mCurMethod == null) {
                 return;
             }
-            if (Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
+            // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
+            // not been changed from the previous call.
+            final boolean isImmediate = (mCursorAnchorInfoMonitorMode &
+                    CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE) != 0;
+            if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
                 Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info=" + cursorAnchorInfo);
                 return;
             }
@@ -1621,6 +1662,9 @@
             try {
                 mCurMethod.updateCursorAnchorInfo(cursorAnchorInfo);
                 mCursorAnchorInfo = cursorAnchorInfo;
+                // Clear immediate bit (if any).
+                mCursorAnchorInfoMonitorMode &=
+                        ~CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE;
             } catch (RemoteException e) {
                 Log.w(TAG, "IME died: " + mCurId, e);
             }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 3a1f6ed..9701c6f 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -68,6 +68,7 @@
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
@@ -5696,6 +5697,11 @@
         public boolean performPrivateCommand(String action, Bundle data) {
             return getTarget().performPrivateCommand(action, data);
         }
+
+        @Override
+        public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+            return getTarget().requestCursorAnchorInfo(request);
+        }
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 170a316..66c4b81 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3027,8 +3027,11 @@
             if (null == imm) {
                 return;
             }
+            if (!imm.isActive(mTextView)) {
+                return;
+            }
             // Skip if the IME has not requested the cursor/anchor position.
-            if (!imm.isWatchingCursor(mTextView)) {
+            if (!imm.isCursorAnchorInfoEnabled()) {
                 return;
             }
             Layout layout = mTextView.getLayout();
diff --git a/core/java/android/widget/LegacyTimePickerDelegate.java b/core/java/android/widget/LegacyTimePickerDelegate.java
index 97f46ea..2216003 100644
--- a/core/java/android/widget/LegacyTimePickerDelegate.java
+++ b/core/java/android/widget/LegacyTimePickerDelegate.java
@@ -224,7 +224,8 @@
         }
 
         mDoneButton = delegator.findViewById(R.id.done_button);
-        if (mDoneButton != null) {
+        mShowDoneButton = (mDoneButton != null);
+        if (mShowDoneButton) {
             mDoneButton.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
@@ -261,7 +262,6 @@
             mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
 
-        mShowDoneButton = false;
         updateDoneButton();
     }
 
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 9a8380d..8ea1090 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -568,22 +568,38 @@
             }
         }
 
-        mTrackDrawable.getPadding(mTempRect);
+        final int trackHeight;
+        final Rect padding = mTempRect;
+        if (mTrackDrawable != null) {
+            mTrackDrawable.getPadding(padding);
+            trackHeight = mTrackDrawable.getIntrinsicHeight();
+        } else {
+            padding.setEmpty();
+            trackHeight = 0;
+        }
+
+        final int thumbWidth;
+        final int thumbHeight;
+        if (mThumbDrawable != null) {
+            thumbWidth = mThumbDrawable.getIntrinsicWidth();
+            thumbHeight = mThumbDrawable.getIntrinsicHeight();
+        } else {
+            thumbWidth = 0;
+            thumbHeight = 0;
+        }
 
         final int maxTextWidth = mShowText ? Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
                 + mThumbTextPadding * 2 : 0;
-        mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
+        mThumbWidth = Math.max(maxTextWidth, thumbWidth);
 
         final int switchWidth = Math.max(mSwitchMinWidth,
-                2 * mThumbWidth + mTempRect.left + mTempRect.right);
-        final int switchHeight = Math.max(mTrackDrawable.getIntrinsicHeight(),
-                mThumbDrawable.getIntrinsicHeight());
-
-
+                2 * mThumbWidth + padding.left + padding.right);
+        final int switchHeight = Math.max(trackHeight, thumbHeight);
         mSwitchWidth = switchWidth;
         mSwitchHeight = switchHeight;
 
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
         final int measuredHeight = getMeasuredHeight();
         if (measuredHeight < switchHeight) {
             setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
@@ -830,32 +846,34 @@
 
     @Override
     public void draw(Canvas c) {
-        final Rect tempRect = mTempRect;
-        final Drawable trackDrawable = mTrackDrawable;
-        final Drawable thumbDrawable = mThumbDrawable;
+        final Rect padding = mTempRect;
 
         // Layout the track.
         final int switchLeft = mSwitchLeft;
         final int switchTop = mSwitchTop;
         final int switchRight = mSwitchRight;
         final int switchBottom = mSwitchBottom;
-        trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
-        trackDrawable.getPadding(tempRect);
+        if (mTrackDrawable != null) {
+            mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
+            mTrackDrawable.getPadding(padding);
+        }
 
-        final int switchInnerLeft = switchLeft + tempRect.left;
+        final int switchInnerLeft = switchLeft + padding.left;
 
         // Relies on mTempRect, MUST be called first!
         final int thumbPos = getThumbOffset();
 
         // Layout the thumb.
-        thumbDrawable.getPadding(tempRect);
-        final int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
-        final int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
-        thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+        if (mThumbDrawable != null) {
+            mThumbDrawable.getPadding(padding);
+            final int thumbLeft = switchInnerLeft - padding.left + thumbPos;
+            final int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + padding.right;
+            mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
 
-        final Drawable background = getBackground();
-        if (background != null) {
-            background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+            final Drawable background = getBackground();
+            if (background != null) {
+                background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+            }
         }
 
         // Draw the background.
@@ -866,35 +884,44 @@
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
-        final Rect tempRect = mTempRect;
+        final Rect padding = mTempRect;
         final Drawable trackDrawable = mTrackDrawable;
-        final Drawable thumbDrawable = mThumbDrawable;
-        trackDrawable.getPadding(tempRect);
+        if (trackDrawable != null) {
+            trackDrawable.getPadding(padding);
+        } else {
+            padding.setEmpty();
+        }
 
         final int switchTop = mSwitchTop;
         final int switchBottom = mSwitchBottom;
-        final int switchInnerLeft = mSwitchLeft + tempRect.left;
-        final int switchInnerTop = switchTop + tempRect.top;
-        final int switchInnerRight = mSwitchRight - tempRect.right;
-        final int switchInnerBottom = switchBottom - tempRect.bottom;
+        final int switchInnerLeft = mSwitchLeft + padding.left;
+        final int switchInnerTop = switchTop + padding.top;
+        final int switchInnerRight = mSwitchRight - padding.right;
+        final int switchInnerBottom = switchBottom - padding.bottom;
 
-        if (mSplitTrack) {
-            final Insets insets = thumbDrawable.getOpticalInsets();
-            thumbDrawable.copyBounds(tempRect);
-            tempRect.left += insets.left;
-            tempRect.right -= insets.right;
+        final Drawable thumbDrawable = mThumbDrawable;
+        if (trackDrawable != null) {
+            if (mSplitTrack && thumbDrawable != null) {
+                final Insets insets = thumbDrawable.getOpticalInsets();
+                thumbDrawable.copyBounds(padding);
+                padding.left += insets.left;
+                padding.right -= insets.right;
 
-            final int saveCount = canvas.save();
-            canvas.clipRect(tempRect, Op.DIFFERENCE);
-            trackDrawable.draw(canvas);
-            canvas.restoreToCount(saveCount);
-        } else {
-            trackDrawable.draw(canvas);
+                final int saveCount = canvas.save();
+                canvas.clipRect(padding, Op.DIFFERENCE);
+                trackDrawable.draw(canvas);
+                canvas.restoreToCount(saveCount);
+            } else {
+                trackDrawable.draw(canvas);
+            }
         }
 
         final int saveCount = canvas.save();
-        canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
-        thumbDrawable.draw(canvas);
+
+        if (thumbDrawable != null) {
+            canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
+            thumbDrawable.draw(canvas);
+        }
 
         final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
         if (switchText != null) {
@@ -904,8 +931,15 @@
             }
             mTextPaint.drawableState = drawableState;
 
-            final Rect thumbBounds = thumbDrawable.getBounds();
-            final int left = (thumbBounds.left + thumbBounds.right) / 2 - switchText.getWidth() / 2;
+            final int cX;
+            if (thumbDrawable != null) {
+                final Rect bounds = thumbDrawable.getBounds();
+                cX = bounds.left + bounds.right;
+            } else {
+                cX = getWidth();
+            }
+
+            final int left = cX / 2 - switchText.getWidth() / 2;
             final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
             canvas.translate(left, top);
             switchText.draw(canvas);
@@ -955,11 +989,12 @@
     }
 
     private int getThumbScrollRange() {
-        if (mTrackDrawable == null) {
+        if (mTrackDrawable != null) {
+            mTrackDrawable.getPadding(mTempRect);
+            return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
+        } else {
             return 0;
         }
-        mTrackDrawable.getPadding(mTempRect);
-        return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
     }
 
     @Override
@@ -1002,16 +1037,6 @@
     }
 
     @Override
-    public void invalidateDrawable(Drawable drawable) {
-        super.invalidateDrawable(drawable);
-
-        if (drawable == mThumbDrawable) {
-            // Handle changes to thumb width and height.
-            requestLayout();
-        }
-    }
-
-    @Override
     protected boolean verifyDrawable(Drawable who) {
         return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
     }
@@ -1019,8 +1044,19 @@
     @Override
     public void jumpDrawablesToCurrentState() {
         super.jumpDrawablesToCurrentState();
-        mThumbDrawable.jumpToCurrentState();
-        mTrackDrawable.jumpToCurrentState();
+
+        if (mThumbDrawable != null) {
+            mThumbDrawable.jumpToCurrentState();
+        }
+
+        if (mTrackDrawable != null) {
+            mTrackDrawable.jumpToCurrentState();
+        }
+
+        if (mPositionAnimator != null && mPositionAnimator.isRunning()) {
+            mPositionAnimator.end();
+            mPositionAnimator = null;
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8e4ba0d..5021960 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.annotation.Widget;
+import android.app.UiModeManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
@@ -29,7 +30,7 @@
 
 import java.util.Locale;
 
-import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.L;
 
 /**
  * A view for selecting the time of day, in either 24 hour or AM/PM mode. The
@@ -91,15 +92,24 @@
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TimePicker,
                 mDefStyleAttr, mDefStyleRes);
 
-        // Create the correct UI delegate. Default is the legacy one.
-        final boolean isLegacyMode = shouldForceLegacyMode() ?
-                true : a.getBoolean(R.styleable.TimePicker_legacyMode, true);
+        // Create the correct UI delegate. Legacy mode is used when API Levels is below L
+        // release or when it is a TV UI
+        final boolean isLegacyMode =  a.getBoolean(
+                R.styleable.TimePicker_legacyMode, isLegacyMode());
+
+        a.recycle();
+
         setLegacyMode(isLegacyMode);
     }
 
-    private boolean shouldForceLegacyMode() {
+    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 < KITKAT;
+        return targetSdkVersion < L;
     }
 
     private TimePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs,
@@ -113,10 +123,7 @@
                 defStyleRes);
     }
 
-    /**
-     * @hide
-     */
-    public void setLegacyMode(boolean isLegacyMode) {
+    private void setLegacyMode(boolean isLegacyMode) {
         removeAllViewsInLayout();
         mDelegate = isLegacyMode ?
                 createLegacyUIDelegate(mContext, mAttrs, mDefStyleAttr, mDefStyleRes) :
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 877938e..a1a64f8 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -43,8 +43,9 @@
             }
         }
         CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
+        int defaultTitleRes = 0;
         if (title == null) {
-            title = getResources().getText(com.android.internal.R.string.chooseActivity);
+            defaultTitleRes = com.android.internal.R.string.chooseActivity;
         }
         Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
         Intent[] initialIntents = null;
@@ -68,6 +69,7 @@
                 initialIntents[i] = in;
             }
         }
-        super.onCreate(savedInstanceState, target, title, initialIntents, null, false);
+        super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
+                null, false);
     }
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 2900595..3e0b021 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -27,6 +27,8 @@
 interface IVoiceInteractor {
     IVoiceInteractorRequest startConfirmation(String callingPackage,
             IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras);
+    IVoiceInteractorRequest startCompleteVoice(String callingPackage,
+            IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
     IVoiceInteractorRequest startAbortVoice(String callingPackage,
             IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
     IVoiceInteractorRequest startCommand(String callingPackage,
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index 8dbf9d4..dcd5759 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,7 @@
 oneway interface IVoiceInteractorCallback {
     void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
             in Bundle result);
+    void deliverCompleteVoiceResult(IVoiceInteractorRequest request, in Bundle result);
     void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result);
     void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
     void deliverCancel(IVoiceInteractorRequest request);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 01b7fde..76ce765 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -18,6 +18,8 @@
 
 import android.app.Activity;
 import android.os.AsyncTask;
+import android.util.ArrayMap;
+import android.widget.AbsListView;
 import android.widget.GridView;
 import com.android.internal.R;
 import com.android.internal.content.PackageMonitor;
@@ -89,6 +91,46 @@
         }
     };
 
+    private enum ActionTitle {
+        VIEW(Intent.ACTION_VIEW,
+                com.android.internal.R.string.whichViewApplication,
+                com.android.internal.R.string.whichViewApplicationNamed),
+        EDIT(Intent.ACTION_EDIT,
+                com.android.internal.R.string.whichEditApplication,
+                com.android.internal.R.string.whichEditApplicationNamed),
+        SEND(Intent.ACTION_SEND,
+                com.android.internal.R.string.whichSendApplication,
+                com.android.internal.R.string.whichSendApplicationNamed),
+        SENDTO(Intent.ACTION_SENDTO,
+                com.android.internal.R.string.whichSendApplication,
+                com.android.internal.R.string.whichSendApplicationNamed),
+        SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
+                com.android.internal.R.string.whichSendApplication,
+                com.android.internal.R.string.whichSendApplicationNamed),
+        DEFAULT(null,
+                com.android.internal.R.string.whichApplication,
+                com.android.internal.R.string.whichApplicationNamed);
+
+        public final String action;
+        public final int titleRes;
+        public final int namedTitleRes;
+
+        ActionTitle(String action, int titleRes, int namedTitleRes) {
+            this.action = action;
+            this.titleRes = titleRes;
+            this.namedTitleRes = namedTitleRes;
+        }
+
+        public static ActionTitle forAction(String action) {
+            for (ActionTitle title : values()) {
+                if (action != null && action.equals(title.action)) {
+                    return title;
+                }
+            }
+            return DEFAULT;
+        }
+    }
+
     private Intent makeMyIntent() {
         Intent intent = new Intent(getIntent());
         intent.setComponent(null);
@@ -113,16 +155,27 @@
                 && categories.contains(Intent.CATEGORY_HOME)) {
             titleResource = com.android.internal.R.string.whichHomeApplication;
         } else {
-            titleResource = com.android.internal.R.string.whichApplication;
+            titleResource = 0;
         }
 
-        onCreate(savedInstanceState, intent, getResources().getText(titleResource),
+        onCreate(savedInstanceState, intent,
+                titleResource != 0 ? getResources().getText(titleResource) : null, titleResource,
                 null, null, true);
     }
 
+    /**
+     * Compatibility version for other bundled services that use this ocerload without
+     * a default title resource
+     */
     protected void onCreate(Bundle savedInstanceState, Intent intent,
-            CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
-            boolean alwaysUseOption) {
+            CharSequence title, Intent[] initialIntents,
+            List<ResolveInfo> rList, boolean alwaysUseOption) {
+        onCreate(savedInstanceState, intent, title, initialIntents, rList, alwaysUseOption);
+    }
+
+    protected void onCreate(Bundle savedInstanceState, Intent intent,
+            CharSequence title, int defaultTitleRes, Intent[] initialIntents,
+            List<ResolveInfo> rList, boolean alwaysUseOption) {
         setTheme(R.style.Theme_DeviceDefault_Resolver);
         super.onCreate(savedInstanceState);
         try {
@@ -132,7 +185,6 @@
             mLaunchedFromUid = -1;
         }
         mPm = getPackageManager();
-        mAlwaysUseOption = alwaysUseOption;
         mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
 
         mPackageMonitor.register(this, getMainLooper(), false);
@@ -143,14 +195,24 @@
         mIconSize = am.getLauncherLargeIconSize();
 
         mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList,
-                mLaunchedFromUid);
+                mLaunchedFromUid, alwaysUseOption);
+
+        final int layoutId;
+        if (mAdapter.hasFilteredItem()) {
+            layoutId = R.layout.resolver_list_with_default;
+            alwaysUseOption = false;
+        } else {
+            layoutId = R.layout.resolver_list;
+        }
+        mAlwaysUseOption = alwaysUseOption;
+
         int count = mAdapter.getCount();
         if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
             // Gulp!
             finish();
             return;
         } else if (count > 1) {
-            setContentView(R.layout.resolver_list);
+            setContentView(layoutId);
             mGridView = (GridView) findViewById(R.id.resolver_list);
             mGridView.setAdapter(mAdapter);
             mGridView.setOnItemClickListener(this);
@@ -162,7 +224,7 @@
 
             resizeGrid();
         } else if (count == 1) {
-            startActivity(mAdapter.intentForPosition(0));
+            startActivity(mAdapter.intentForPosition(0, false));
             mPackageMonitor.unregister();
             mRegistered = false;
             finish();
@@ -189,10 +251,19 @@
 
         final TextView titleView = (TextView) findViewById(R.id.title);
         if (titleView != null) {
+            if (title == null) {
+                title = getTitleForAction(intent.getAction(), defaultTitleRes);
+            }
             titleView.setText(title);
         }
 
-        if (alwaysUseOption) {
+        final ImageView iconView = (ImageView) findViewById(R.id.icon);
+        final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
+        if (iconView != null && iconInfo != null) {
+            new LoadIconIntoViewTask(iconView).execute(iconInfo);
+        }
+
+        if (alwaysUseOption || mAdapter.hasFilteredItem()) {
             final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
             if (buttonLayout != null) {
                 buttonLayout.setVisibility(View.VISIBLE);
@@ -201,12 +272,22 @@
             } else {
                 mAlwaysUseOption = false;
             }
-            // Set the initial highlight if there was a preferred or last used choice
-            final int initialHighlight = mAdapter.getInitialHighlight();
-            if (initialHighlight >= 0) {
-                mGridView.setItemChecked(initialHighlight, true);
-                onItemClick(null, null, initialHighlight, 0); // Other entries are not used
-            }
+        }
+
+        if (mAdapter.hasFilteredItem()) {
+            setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
+            mOnceButton.setEnabled(true);
+        }
+    }
+
+    protected CharSequence getTitleForAction(String action, int defaultTitleRes) {
+        final ActionTitle title = ActionTitle.forAction(action);
+        final boolean named = mAdapter.hasFilteredItem();
+        if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
+            return getString(defaultTitleRes);
+        } else {
+            return named ? getString(title.namedTitleRes, mAdapter.getFilteredItem().displayLabel) :
+                    getString(title.titleRes);
         }
     }
 
@@ -292,7 +373,7 @@
             final int checkedPos = mGridView.getCheckedItemPosition();
             final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
             mLastSelected = checkedPos;
-            setAlwaysButtonEnabled(hasValidSelection, checkedPos);
+            setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
             mOnceButton.setEnabled(hasValidSelection);
             if (hasValidSelection) {
                 mGridView.setSelection(checkedPos);
@@ -305,21 +386,22 @@
         final int checkedPos = mGridView.getCheckedItemPosition();
         final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
         if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
-            setAlwaysButtonEnabled(hasValidSelection, checkedPos);
+            setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
             mOnceButton.setEnabled(hasValidSelection);
             if (hasValidSelection) {
                 mGridView.smoothScrollToPosition(checkedPos);
             }
             mLastSelected = checkedPos;
         } else {
-            startSelected(position, false);
+            startSelected(position, false, true);
         }
     }
 
-    private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos) {
+    private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
+            boolean filtered) {
         boolean enabled = false;
         if (hasValidSelection) {
-            ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos);
+            ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered);
             if (ri.targetUserId == UserHandle.USER_CURRENT) {
                 enabled = true;
             }
@@ -329,22 +411,25 @@
 
     public void onButtonClick(View v) {
         final int id = v.getId();
-        startSelected(mGridView.getCheckedItemPosition(), id == R.id.button_always);
+        startSelected(mAlwaysUseOption ?
+                mGridView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
+                id == R.id.button_always,
+                mAlwaysUseOption);
         dismiss();
     }
 
-    void startSelected(int which, boolean always) {
+    void startSelected(int which, boolean always, boolean filtered) {
         if (isFinishing()) {
             return;
         }
-        ResolveInfo ri = mAdapter.resolveInfoForPosition(which);
-        Intent intent = mAdapter.intentForPosition(which);
+        ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
+        Intent intent = mAdapter.intentForPosition(which, filtered);
         onIntentSelected(ri, intent, always);
         finish();
     }
 
     protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
-        if (mAlwaysUseOption && mAdapter.mOrigResolveList != null) {
+        if ((mAlwaysUseOption || mAdapter.hasFilteredItem()) && mAdapter.mOrigResolveList != null) {
             // Build a reasonable intent filter, based on what matched.
             IntentFilter filter = new IntentFilter();
 
@@ -485,16 +570,19 @@
         List<DisplayResolveInfo> mList;
         List<ResolveInfo> mOrigResolveList;
 
-        private int mInitialHighlight = -1;
+        private int mLastChosenPosition = -1;
+        private boolean mFilterLastUsed;
 
         public ResolveListAdapter(Context context, Intent intent,
-                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid) {
+                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
+                boolean filterLastUsed) {
             mIntent = new Intent(intent);
             mInitialIntents = initialIntents;
             mBaseResolveList = rList;
             mLaunchedFromUid = launchedFromUid;
             mInflater = LayoutInflater.from(context);
             mList = new ArrayList<DisplayResolveInfo>();
+            mFilterLastUsed = filterLastUsed;
             rebuildList();
         }
 
@@ -511,8 +599,23 @@
             }
         }
 
-        public int getInitialHighlight() {
-            return mInitialHighlight;
+        public DisplayResolveInfo getFilteredItem() {
+            if (mFilterLastUsed && mLastChosenPosition >= 0) {
+                // Not using getItem since it offsets to dodge this position for the list
+                return mList.get(mLastChosenPosition);
+            }
+            return null;
+        }
+
+        public int getFilteredPosition() {
+            if (mFilterLastUsed && mLastChosenPosition >= 0) {
+                return mLastChosenPosition;
+            }
+            return AbsListView.INVALID_POSITION;
+        }
+
+        public boolean hasFilteredItem() {
+            return mFilterLastUsed && mLastChosenPosition >= 0;
         }
 
         private void rebuildList() {
@@ -532,7 +635,7 @@
             } else {
                 currentResolveList = mOrigResolveList = mPm.queryIntentActivities(
                         mIntent, PackageManager.MATCH_DEFAULT_ONLY
-                        | (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0));
+                        | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0));
                 // Filter out any activities that the launched uid does not
                 // have permission for.  We don't do this when we have an explicit
                 // list of resolved activities, because that only happens when
@@ -648,7 +751,7 @@
                         && mLastChosen.activityInfo.packageName.equals(
                                 ro.activityInfo.packageName)
                         && mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) {
-                    mInitialHighlight = mList.size();
+                    mLastChosenPosition = mList.size();
                 }
                 // No duplicate labels. Use label for entry at start
                 mList.add(new DisplayResolveInfo(ro, roLabel, null, null));
@@ -683,7 +786,7 @@
                             && mLastChosen.activityInfo.packageName.equals(
                                     add.activityInfo.packageName)
                             && mLastChosen.activityInfo.name.equals(add.activityInfo.name)) {
-                        mInitialHighlight = mList.size();
+                        mLastChosenPosition = mList.size();
                     }
                     if (usePkg) {
                         // Use application name for all entries from start to end-1
@@ -698,12 +801,12 @@
             }
         }
 
-        public ResolveInfo resolveInfoForPosition(int position) {
-            return mList.get(position).ri;
+        public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
+            return (filtered ? getItem(position) : mList.get(position)).ri;
         }
 
-        public Intent intentForPosition(int position) {
-            DisplayResolveInfo dri = mList.get(position);
+        public Intent intentForPosition(int position, boolean filtered) {
+            DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position);
             
             Intent intent = new Intent(dri.origIntent != null
                     ? dri.origIntent : mIntent);
@@ -716,10 +819,17 @@
         }
 
         public int getCount() {
-            return mList.size();
+            int result = mList.size();
+            if (mFilterLastUsed && mLastChosenPosition >= 0) {
+                result--;
+            }
+            return result;
         }
 
-        public Object getItem(int position) {
+        public DisplayResolveInfo getItem(int position) {
+            if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
+                position++;
+            }
             return mList.get(position);
         }
 
@@ -742,7 +852,7 @@
             } else {
                 view = convertView;
             }
-            bindView(view, mList.get(position));
+            bindView(view, getItem(position));
             return view;
         }
 
@@ -778,7 +888,7 @@
 
         @Override
         public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-            ResolveInfo ri = mAdapter.resolveInfoForPosition(position);
+            ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
             showAppDetails(ri);
             return true;
         }
@@ -800,5 +910,27 @@
             mAdapter.notifyDataSetChanged();
         }
     }
+
+    class LoadIconIntoViewTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
+        final ImageView mTargetView;
+
+        public LoadIconIntoViewTask(ImageView target) {
+            mTargetView = target;
+        }
+
+        @Override
+        protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
+            final DisplayResolveInfo info = params[0];
+            if (info.displayIcon == null) {
+                info.displayIcon = loadIconForResolveInfo(info.ri);
+            }
+            return info;
+        }
+
+        @Override
+        protected void onPostExecute(DisplayResolveInfo info) {
+            mTargetView.setImageDrawable(info.displayIcon);
+        }
+    }
 }
 
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 7af52f3..50effd0 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -65,6 +65,7 @@
         mToolbar = toolbar;
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar);
         mWindowCallback = windowCallback;
+        mDecorToolbar.setWindowCallback(mWindowCallback);
         toolbar.setOnMenuItemClickListener(mMenuClicker);
         mDecorToolbar.setWindowTitle(title);
     }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index ac3274d..d801571 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -262,6 +262,7 @@
         final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
         final String systemLocale = res.getConfiguration().locale.toString();
         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
+        final String systemLanguage = res.getConfiguration().locale.getLanguage();
         final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
                 new HashMap<String, InputMethodSubtype>();
         final int N = subtypes.size();
@@ -282,15 +283,22 @@
             final InputMethodSubtype subtype = subtypes.get(i);
             final String locale = subtype.getLocale();
             final String mode = subtype.getMode();
+            final String language = getLanguageFromLocaleString(locale);
             // When system locale starts with subtype's locale, that subtype will be applicable
-            // for system locale
+            // for system locale. We need to make sure the languages are the same, to prevent
+            // locales like "fil" (Filipino) being matched by "fi" (Finnish).
+            //
             // For instance, it's clearly applicable for cases like system locale = en_US and
             // subtype = en, but it is not necessarily considered applicable for cases like system
             // locale = en and subtype = en_US.
+            //
             // We just call systemLocale.startsWith(locale) in this function because there is no
             // need to find applicable subtypes aggressively unlike
             // findLastResortApplicableSubtypeLocked.
-            if (systemLocale.startsWith(locale)) {
+            //
+            // TODO: This check is broken. It won't take scripts into account and doesn't
+            // account for the mandatory conversions performed by Locale#toString.
+            if (language.equals(systemLanguage) && systemLocale.startsWith(locale)) {
                 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
                 // If more applicable subtypes are contained, skip.
                 if (applicableSubtype != null) {
@@ -335,6 +343,18 @@
     }
 
     /**
+     * Returns the language component of a given locale string.
+     */
+    public static String getLanguageFromLocaleString(String locale) {
+        final int idx = locale.indexOf('_');
+        if (idx < 0) {
+            return locale;
+        } else {
+            return locale.substring(0, idx);
+        }
+    }
+
+    /**
      * If there are no selected subtypes, tries finding the most applicable one according to the
      * given locale.
      * @param subtypes this function will search the most applicable subtype in subtypes
@@ -353,7 +373,7 @@
         if (TextUtils.isEmpty(locale)) {
             locale = res.getConfiguration().locale.toString();
         }
-        final String language = locale.substring(0, 2);
+        final String language = getLanguageFromLocaleString(locale);
         boolean partialMatchFound = false;
         InputMethodSubtype applicableSubtype = null;
         InputMethodSubtype firstMatchedModeSubtype = null;
@@ -361,6 +381,7 @@
         for (int i = 0; i < N; ++i) {
             InputMethodSubtype subtype = subtypes.get(i);
             final String subtypeLocale = subtype.getLocale();
+            final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
             // An applicable subtype should match "mode". If mode is null, mode will be ignored,
             // and all subtypes with all modes can be candidates.
             if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
@@ -371,7 +392,7 @@
                     // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
                     applicableSubtype = subtype;
                     break;
-                } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
+                } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
                     // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
                     applicableSubtype = subtype;
                     partialMatchFound = true;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 273de61..023ba03 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -48,6 +48,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Arrays;
 
 /**
  * A helper class for retrieving the power usage information for all applications and services.
@@ -82,7 +83,6 @@
     private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>();
 
     private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
-    private int mAsUser = 0;
 
     long mRawRealtime;
     long mRawUptime;
@@ -176,11 +176,34 @@
      * Refreshes the power usage list.
      */
     public void refreshStats(int statsType, int asUser) {
-        refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000,
+        SparseArray<UserHandle> users = new SparseArray<UserHandle>(1);
+        users.put(asUser, new UserHandle(asUser));
+        refreshStats(statsType, users);
+    }
+
+    /**
+     * Refreshes the power usage list.
+     */
+    public void refreshStats(int statsType, List<UserHandle> asUsers) {
+        final int n = asUsers.size();
+        SparseArray<UserHandle> users = new SparseArray<UserHandle>(n);
+        for (int i = 0; i < n; ++i) {
+            UserHandle userHandle = asUsers.get(i);
+            users.put(userHandle.getIdentifier(), userHandle);
+        }
+        refreshStats(statsType, users);
+    }
+
+    /**
+     * Refreshes the power usage list.
+     */
+    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
+        refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
                 SystemClock.uptimeMillis() * 1000);
     }
 
-    public void refreshStats(int statsType, int asUser, long rawRealtimeUs, long rawUptimeUs) {
+    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
+            long rawUptimeUs) {
         // Initialize mStats if necessary.
         getStats();
 
@@ -204,7 +227,6 @@
         }
 
         mStatsType = statsType;
-        mAsUser = asUser;
         mRawUptime = rawUptimeUs;
         mRawRealtime = rawRealtimeUs;
         mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs);
@@ -227,7 +249,7 @@
         mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
                 * mPowerProfile.getBatteryCapacity()) / 100;
 
-        processAppUsage();
+        processAppUsage(asUsers);
 
         // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
         for (int i=0; i<mUsageList.size(); i++) {
@@ -280,7 +302,8 @@
         Collections.sort(mUsageList);
     }
 
-    private void processAppUsage() {
+    private void processAppUsage(SparseArray<UserHandle> asUsers) {
+        final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
         SensorManager sensorManager = (SensorManager) mContext.getSystemService(
                 Context.SENSOR_SERVICE);
         final int which = mStatsType;
@@ -499,7 +522,7 @@
                 } else if (u.getUid() == Process.BLUETOOTH_UID) {
                     mBluetoothSippers.add(app);
                     mBluetoothPower += power;
-                } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser
+                } else if (!forAllUsers && asUsers.get(userId) == null
                         && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
                     List<BatterySipper> list = mUserSippers.get(userId);
                     if (list == null) {
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index c792d78..897381d 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -25,6 +25,7 @@
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 
@@ -54,6 +55,7 @@
     private static final int DO_REPORT_FULLSCREEN_MODE = 100;
     private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
     private static final int DO_CLEAR_META_KEY_STATES = 130;
+    private static final int DO_REQUEST_CURSOR_ANCHOR_INFO = 140;
 
     private WeakReference<InputConnection> mInputConnection;
 
@@ -175,6 +177,11 @@
         dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
     }
 
+    public void requestCursorAnchorInfo(CursorAnchorInfoRequest request, int seq,
+            IInputContextCallback callback) {
+        dispatchMessage(obtainMessageOSC(DO_REQUEST_CURSOR_ANCHOR_INFO, request, seq, callback));
+    }
+
     void dispatchMessage(Message msg) {
         // If we are calling this from the main thread, then we can call
         // right through.  Otherwise, we need to send the message to the
@@ -420,6 +427,23 @@
                         (Bundle)args.arg2);
                 return;
             }
+            case DO_REQUEST_CURSOR_ANCHOR_INFO: {
+                SomeArgs args = (SomeArgs)msg.obj;
+                try {
+                    InputConnection ic = mInputConnection.get();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+                        args.callback.setRequestCursorAnchorInfoResult(0, args.seq);
+                        return;
+                    }
+                    args.callback.setRequestCursorAnchorInfoResult(
+                            ic.requestCursorAnchorInfo((CursorAnchorInfoRequest)args.arg1),
+                            args.seq);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
+                }
+                return;
+            }
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -449,7 +473,15 @@
         args.seq = seq;
         return mH.obtainMessage(what, arg1, arg2, args);
     }
-    
+
+    Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) {
+        SomeArgs args = new SomeArgs();
+        args.arg1 = arg1;
+        args.callback = callback;
+        args.seq = seq;
+        return mH.obtainMessage(what, 0, 0, args);
+    }
+
     Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
             IInputContextCallback callback) {
         SomeArgs args = new SomeArgs();
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index 719a24f..c06596a 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -20,6 +20,7 @@
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
 import android.view.inputmethod.ExtractedTextRequest;
 
 import com.android.internal.view.IInputContextCallback;
@@ -73,4 +74,6 @@
 
     void getSelectedText(int flags, int seq, IInputContextCallback callback);
 
+    void requestCursorAnchorInfo(in CursorAnchorInfoRequest request, int seq,
+            IInputContextCallback callback);
 }
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
index 661066b..ab2fbdc 100644
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ b/core/java/com/android/internal/view/IInputContextCallback.aidl
@@ -27,4 +27,5 @@
     void setCursorCapsMode(int capsMode, int seq);
     void setExtractedText(in ExtractedText extractedText, int seq);
     void setSelectedText(CharSequence selectedText, int seq);
+    void setRequestCursorAnchorInfoResult(int result, int seq);
 }
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index b100d27..89d36ff 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -27,6 +27,5 @@
     void onBindMethod(in InputBindResult res);
     void onUnbindMethod(int sequence);
     void setActive(boolean active);
-    void setCursorAnchorMonitorMode(int monitorMode);
     void setUserActionNotificationSequenceNumber(int sequenceNumber);
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index b84c359..6f104dd 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -78,5 +78,4 @@
     void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
     int getInputMethodWindowVisibleHeight();
     oneway void notifyUserAction(int sequenceNumber);
-    void setCursorAnchorMonitorMode(in IBinder token, int monitorMode);
 }
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 9024d8d..8535a98 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -23,6 +23,7 @@
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
@@ -40,6 +41,7 @@
         public CharSequence mSelectedText;
         public ExtractedText mExtractedText;
         public int mCursorCapsMode;
+        public int mCursorAnchorInfoRequestResult;
         
         // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
         // exclusive access to this object.
@@ -152,7 +154,20 @@
                 }
             }
         }
-        
+
+        public void setRequestCursorAnchorInfoResult(int result, int seq) {
+            synchronized (this) {
+                if (seq == mSeq) {
+                    mCursorAnchorInfoRequestResult = result;
+                    mHaveValue = true;
+                    notifyAll();
+                } else {
+                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+                            + ") in setCursorAnchorInfoRequestResult, ignoring.");
+                }
+            }
+        }
+
         /**
          * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
          * 
@@ -413,4 +428,22 @@
             return false;
         }
     }
+
+    public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+        int value = CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
+        try {
+            InputContextCallback callback = InputContextCallback.getInstance();
+            mIInputContext.requestCursorAnchorInfo(request, callback.mSeq, callback);
+            synchronized (callback) {
+                callback.waitForResultLocked();
+                if (callback.mHaveValue) {
+                    value = callback.mCursorAnchorInfoRequestResult;
+                }
+            }
+            callback.dispose();
+        } catch (RemoteException e) {
+            return CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
+        }
+        return value;
+    }
 }
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 4cbdf78..10a7794 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -25,6 +25,7 @@
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.widget.TextView;
@@ -185,4 +186,18 @@
 
         return success;
     }
+
+    @Override
+    public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+        if (DEBUG) Log.v(TAG, "requestCursorAnchorInfo " + request);
+        final int result = super.requestCursorAnchorInfo(request);
+        if (mIMM != null && request != null && (request.getRequestType() ==
+                CursorAnchorInfoRequest.TYPE_CURSOR_ANCHOR_INFO)) {
+            mIMM.setCursorAnchorInfoMonitorMode(request.getRequestFlags());
+            // One-shot event is not yet fully supported.
+            // TODO: Support one-shot event correctly.
+            return CursorAnchorInfoRequest.RESULT_SCHEDULED;
+        }
+        return result;
+    }
 }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ef7ef0a..0e22174 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -147,6 +147,7 @@
 	android_hardware_UsbDevice.cpp \
 	android_hardware_UsbDeviceConnection.cpp \
 	android_hardware_UsbRequest.cpp \
+	android_hardware_location_ActivityRecognitionHardware.cpp \
 	android_util_FileObserver.cpp \
 	android/opengl/poly_clip.cpp.arm \
 	android/opengl/util.cpp.arm \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9b66734..92a8fca 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -87,6 +87,7 @@
 extern int register_android_hardware_UsbDevice(JNIEnv *env);
 extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
+extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
 
 extern int register_android_media_AudioRecord(JNIEnv *env);
 extern int register_android_media_AudioSystem(JNIEnv *env);
@@ -1323,6 +1324,7 @@
     REG_JNI(register_android_hardware_UsbDevice),
     REG_JNI(register_android_hardware_UsbDeviceConnection),
     REG_JNI(register_android_hardware_UsbRequest),
+    REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
     REG_JNI(register_android_media_AudioRecord),
     REG_JNI(register_android_media_AudioSystem),
     REG_JNI(register_android_media_AudioTrack),
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 0cfcaef..c71f2261 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -56,19 +56,20 @@
     SkSafeUnref(shader);
 }
 
-static void Shader_setLocalMatrix(JNIEnv* env, jobject o, jlong shaderHandle,
+static jlong Shader_setLocalMatrix(JNIEnv* env, jobject o, jlong shaderHandle,
         jlong matrixHandle)
 {
     SkShader* shader       = reinterpret_cast<SkShader*>(shaderHandle);
     const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
     if (shader) {
         if (NULL == matrix) {
-            shader->resetLocalMatrix();
+            matrix = &SkMatrix::I();
         }
-        else {
-            shader->setLocalMatrix(*matrix);
-        }
+        SkShader* newShader = SkShader::CreateLocalMatrixShader(shader, *matrix);
+        shader->unref();
+        shader = newShader;
     }
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -239,7 +240,7 @@
 
 static JNINativeMethod gShaderMethods[] = {
     { "nativeDestructor",        "(J)V",    (void*)Shader_destructor        },
-    { "nativeSetLocalMatrix",    "(JJ)V",   (void*)Shader_setLocalMatrix    }
+    { "nativeSetLocalMatrix",    "(JJ)J",   (void*)Shader_setLocalMatrix    }
 };
 
 static JNINativeMethod gBitmapShaderMethods[] = {
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
new file mode 100644
index 0000000..5b542ba
--- /dev/null
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ActivityRecognitionHardware"
+
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+#include "activity_recognition.h"
+
+
+// keep base connection data from the HAL
+static activity_recognition_module_t* sModule = NULL;
+static activity_recognition_device_t* sDevice = NULL;
+
+static jobject sCallbacksObject = NULL;
+static jmethodID sOnActivityChanged = NULL;
+
+
+static void check_and_clear_exceptions(JNIEnv* env, const char* method_name) {
+    if (!env->ExceptionCheck()) {
+        return;
+    }
+
+    ALOGE("An exception was thrown by '%s'.", method_name);
+    LOGE_EX(env);
+    env->ExceptionClear();
+}
+
+static jint attach_thread(JNIEnv** env) {
+    JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
+    assert(java_vm != NULL);
+
+    JavaVMAttachArgs args = {
+        JNI_VERSION_1_6,
+        "ActivityRecognition HAL callback.",
+        NULL /* group */
+    };
+
+    jint result = java_vm->AttachCurrentThread(env, &args);
+    if (result != JNI_OK) {
+        ALOGE("Attach to callback thread failed: %d", result);
+    }
+
+    return result;
+}
+
+static jint detach_thread() {
+    JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
+    assert(java_vm != NULL);
+
+    jint result = java_vm->DetachCurrentThread();
+    if (result != JNI_OK) {
+        ALOGE("Detach of callback thread failed: %d", result);
+    }
+
+    return result;
+}
+
+
+/**
+ * Handle activity recognition events from HAL.
+ */
+static void activity_callback(
+        const activity_recognition_callback_procs_t* procs,
+        const activity_event_t* events,
+        int count) {
+    if (sOnActivityChanged == NULL) {
+        ALOGE("Dropping activity_callback because onActivityChanged handler is null.");
+        return;
+    }
+
+    if (events == NULL || count <= 0) {
+        ALOGE("Invalid activity_callback. Count: %d, Events: %p", count, events);
+        return;
+    }
+
+    JNIEnv* env = NULL;
+    int result = attach_thread(&env);
+    if (result != JNI_OK) {
+        return;
+    }
+
+    jclass event_class =
+            env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
+    jmethodID event_ctor = env->GetMethodID(event_class, "<init>", "()V");
+    jfieldID activity_field = env->GetFieldID(event_class, "activity", "I");
+    jfieldID type_field = env->GetFieldID(event_class, "type", "I");
+    jfieldID timestamp_field = env->GetFieldID(event_class, "timestamp", "J");
+
+    jobjectArray events_array = env->NewObjectArray(count, event_class, NULL);
+    for (int i = 0; i < count; ++i) {
+        const activity_event_t* event = &events[i];
+        jobject event_object = env->NewObject(event_class, event_ctor);
+        env->SetIntField(event_object, activity_field, event->activity);
+        env->SetIntField(event_object, type_field, event->event_type);
+        env->SetLongField(event_object, timestamp_field, event->timestamp);
+        env->SetObjectArrayElement(events_array, i, event_object);
+        env->DeleteLocalRef(event_object);
+    }
+
+    env->CallVoidMethod(sCallbacksObject, sOnActivityChanged, events_array);
+    check_and_clear_exceptions(env, __FUNCTION__);
+
+    // TODO: ideally we'd let the HAL register the callback thread only once
+    detach_thread();
+}
+
+activity_recognition_callback_procs_t sCallbacks {
+    activity_callback,
+};
+
+/**
+ * Initializes the ActivityRecognitionHardware class from the native side.
+ */
+static void class_init(JNIEnv* env, jclass clazz) {
+    // open the hardware module
+    int error = hw_get_module(
+            ACTIVITY_RECOGNITION_HARDWARE_MODULE_ID,
+            (const hw_module_t**) &sModule);
+    if (error != 0) {
+        ALOGE("Error hw_get_module: %d", error);
+        return;
+    }
+
+    error = activity_recognition_open(&sModule->common, &sDevice);
+    if (error != 0) {
+        ALOGE("Error opening device: %d", error);
+        return;
+    }
+
+    // get references to the Java provided methods
+    sOnActivityChanged = env->GetMethodID(
+            clazz,
+            "onActivityChanged",
+            "([Landroid/hardware/location/ActivityRecognitionHardware$Event;)V");
+    if (sOnActivityChanged == NULL) {
+        ALOGE("Error obtaining ActivityChanged callback.");
+        return;
+    }
+
+    // register callbacks
+    sDevice->register_activity_callback(sDevice, &sCallbacks);
+}
+
+/**
+ * Initializes and connect the callbacks handlers in the HAL.
+ */
+static void initialize(JNIEnv* env, jobject obj) {
+    if (sCallbacksObject == NULL) {
+        sCallbacksObject = env->NewGlobalRef(obj);
+    } else {
+        ALOGD("Callbacks Object was already initialized.");
+    }
+
+    if (sDevice != NULL) {
+        sDevice->register_activity_callback(sDevice, &sCallbacks);
+    } else {
+        ALOGD("ActivityRecognition device not found during initialization.");
+    }
+}
+
+/**
+ * De-initializes the ActivityRecognitionHardware from the native side.
+ */
+static void release(JNIEnv* env, jobject obj) {
+    if (sDevice == NULL) {
+        return;
+    }
+
+    int error = activity_recognition_close(sDevice);
+    if (error != 0) {
+        ALOGE("Error closing device: %d", error);
+        return;
+    }
+}
+
+/**
+ * Returns true if ActivityRecognition HAL is supported, false otherwise.
+ */
+static jboolean is_supported(JNIEnv* env, jclass clazz) {
+    if (sModule != NULL && sDevice != NULL ) {
+        return JNI_TRUE;
+    }
+    return JNI_FALSE;
+}
+
+/**
+ * Gets an array representing the supported activities.
+ */
+static jobjectArray get_supported_activities(JNIEnv* env, jobject obj) {
+    if (sModule == NULL) {
+        return NULL;
+    }
+
+    char const* const* list = NULL;
+    int list_size = sModule->get_supported_activities_list(sModule, &list);
+    if (list_size <= 0 || list == NULL) {
+        return NULL;
+    }
+
+    jclass string_class = env->FindClass("java/lang/String;");
+    if (string_class == NULL) {
+        ALOGE("Unable to find String class for supported activities.");
+        return NULL;
+    }
+
+    jobjectArray string_array = env->NewObjectArray(list_size, string_class, NULL);
+    if (string_array == NULL) {
+        ALOGE("Unable to create string array for supported activities.");
+        return NULL;
+    }
+
+    for (int i = 0; i < list_size; ++i) {
+        const char* string_ptr = const_cast<const char*>(list[i]);
+        jsize string_length = strlen(string_ptr);
+        jstring string = env->NewString((const jchar*) string_ptr, string_length);
+        env->SetObjectArrayElement(string_array, i, string);
+
+        // log debugging information in case we need to try to trace issues with the strings
+        if (string_length) {
+            ALOGD("Invalid activity (index=%d) name size: %d", i, string_length);
+        }
+    }
+
+    return string_array;
+}
+
+/**
+ * Enables a given activity event to be actively monitored.
+ */
+static int enable_activity_event(
+        JNIEnv* env,
+        jobject obj,
+        jint activity_handle,
+        jint event_type,
+        jlong report_latency_ns) {
+    return sDevice->enable_activity_event(
+            sDevice,
+            (uint32_t) activity_handle,
+            (uint32_t) event_type,
+            report_latency_ns);
+}
+
+/**
+ * Disables a given activity event from being actively monitored.
+ */
+static int disable_activity_event(
+        JNIEnv* env,
+        jobject obj,
+        jint activity_handle,
+        jint event_type) {
+    return sDevice->disable_activity_event(
+            sDevice,
+            (uint32_t) activity_handle,
+            (uint32_t) event_type);
+}
+
+/**
+ * Request flush for al batch buffers.
+ */
+static int flush(JNIEnv* env, jobject obj) {
+    return sDevice->flush(sDevice);
+}
+
+
+static JNINativeMethod sMethods[] = {
+    // {"name", "signature", (void*) functionPointer },
+    { "nativeClassInit", "()V", (void*) class_init },
+    { "nativeInitialize", "()V", (void*) initialize },
+    { "nativeRelease", "()V", (void*) release },
+    { "nativeIsSupported", "()Z", (void*) is_supported },
+    { "nativeGetSupportedActivities", "()[Ljava/lang/String;", (void*) get_supported_activities },
+    { "nativeEnableActivityEvent", "(IIJ)I", (void*) enable_activity_event },
+    { "nativeDisableActivityEvent", "(II)I", (void*) disable_activity_event },
+    { "nativeFlush", "()I", (void*) flush },
+};
+
+/**
+ * Registration method invoked in JNI load.
+ */
+int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env) {
+    return jniRegisterNativeMethods(
+            env,
+            "android/hardware/location/ActivityRecognitionHardware",
+            sMethods,
+            NELEM(sMethods));
+}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index a75d547..760ed45 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -246,23 +246,11 @@
     return env->NewStringUTF(::dhcp_get_errmsg());
 }
 
-static void android_net_utils_markSocket(JNIEnv *env, jobject thiz, jint socket, jint mark)
-{
-    if (setsockopt(socket, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
-        jniThrowException(env, "java/lang/IllegalStateException", "Error marking socket");
-    }
-}
-
 static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
 {
     return (jboolean) !setNetworkForProcess(netId);
 }
 
-static jboolean android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
-{
-    return (jboolean) !setNetworkForProcess(NETID_UNSET);
-}
-
 static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz)
 {
     return getNetworkForProcess();
@@ -274,11 +262,6 @@
     return (jboolean) !setNetworkForResolv(netId);
 }
 
-static jboolean android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
-{
-    return (jboolean) !setNetworkForResolv(NETID_UNSET);
-}
-
 static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
         jint netId)
 {
@@ -306,12 +289,9 @@
     { "stopDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_stopDhcp },
     { "releaseDhcpLease", "(Ljava/lang/String;)Z",  (void *)android_net_utils_releaseDhcpLease },
     { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
-    { "markSocket", "(II)V", (void*) android_net_utils_markSocket },
     { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
     { "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
-    { "unbindProcessToNetwork", "()Z", (void*) android_net_utils_unbindProcessToNetwork },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
-    { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
     { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork },
     { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
 };
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c34a971..4725cfb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2602,16 +2602,6 @@
         android:description="@string/permdesc_modifyNetworkAccounting"
         android:protectionLevel="signature|system" />
 
-    <!-- @SystemApi Allows an application to mark traffic as from another user for per user routing.
-         Used by system wide services like media server that execute delegated network connections
-         for users.
-         @hide
-    -->
-    <permission android:name="android.permission.MARK_NETWORK_SOCKET"
-        android:label="@string/permlab_markNetworkSocket"
-        android:description="@string/permdesc_markNetworkSocket"
-        android:protectionLevel="signature|system" />
-
     <!-- C2DM permission.
          @hide Used internally.
      -->
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 773b386..8e57543 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -20,7 +20,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:maxWidth="400dp"
+    android:maxWidth="@dimen/resolver_max_width"
     android:maxCollapsedHeight="260dp"
     android:maxCollapsedHeightSmall="56dp"
     android:id="@id/contentPanel"
@@ -30,7 +30,7 @@
               android:layout_width="match_parent"
               android:layout_height="?android:attr/listPreferredItemHeight"
               android:layout_alwaysShow="true"
-              android:textAppearance="?android:attr/textAppearanceLarge"
+              android:textAppearance="?android:attr/textAppearanceMedium"
               android:gravity="start|center_vertical"
               android:paddingLeft="32dp"
               android:paddingRight="32dp"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
new file mode 100644
index 0000000..0bd0e14
--- /dev/null
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -0,0 +1,119 @@
+<?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.
+*/
+-->
+<com.android.internal.widget.ResolverDrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:maxWidth="@dimen/resolver_max_width"
+    android:maxCollapsedHeight="48dp"
+    android:id="@id/contentPanel"
+    >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alwaysShow="true"
+        android:orientation="vertical"
+        android:background="@color/white"
+        android:elevation="8dp" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="80dp"
+            android:paddingStart="32dp"
+            android:paddingEnd="32dp"
+            android:orientation="horizontal"
+            >
+
+            <TextView android:id="@+id/title"
+                      android:layout_width="0dp"
+                      android:layout_weight="1"
+                      android:layout_height="?android:attr/listPreferredItemHeight"
+                      android:textAppearance="?android:attr/textAppearanceMedium"
+                      android:gravity="start|center_vertical"
+                      android:paddingEnd="16dp"
+                      />
+            <ImageView android:id="@+id/icon"
+                       android:layout_width="56dp"
+                       android:layout_height="56dp"
+                       android:layout_gravity="center_vertical"
+                       android:scaleType="fitCenter"
+                       />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/button_bar"
+            android:visibility="gone"
+            style="?android:attr/buttonBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alwaysShow="true"
+            android:gravity="end"
+            android:orientation="horizontal"
+            android:layoutDirection="locale"
+            android:measureWithLargestChild="true"
+            android:paddingBottom="16dp"
+            android:paddingStart="32dp"
+            android:paddingEnd="32dp"
+            android:background="@color/white"
+            android:elevation="8dp">
+            <Button android:id="@+id/button_once"
+                    android:layout_width="wrap_content"
+                    android:layout_gravity="start"
+                    android:maxLines="2"
+                    style="?android:attr/buttonBarNegativeButtonStyle"
+                    android:minHeight="@dimen/alert_dialog_button_bar_height"
+                    android:layout_height="wrap_content"
+                    android:enabled="false"
+                    android:text="@string/activity_resolver_use_once"
+                    android:onClick="onButtonClick" />
+            <Button android:id="@+id/button_always"
+                    android:layout_width="wrap_content"
+                    android:layout_gravity="end"
+                    android:maxLines="2"
+                    android:minHeight="@dimen/alert_dialog_button_bar_height"
+                    style="?android:attr/buttonBarPositiveButtonStyle"
+                    android:layout_height="wrap_content"
+                    android:enabled="false"
+                    android:text="@string/activity_resolver_use_always"
+                    android:onClick="onButtonClick" />
+        </LinearLayout>
+        <View android:layout_width="match_parent"
+              android:layout_height="1dp"
+              android:background="?android:attr/dividerVertical" />
+    </LinearLayout>
+
+    <GridView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/resolver_list"
+        android:numColumns="4"
+        android:columnWidth="128dp"
+        android:clipToPadding="false"
+        android:scrollbarStyle="outsideOverlay"
+        android:paddingLeft="32dp"
+        android:paddingRight="32dp"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:background="@color/white"
+        android:elevation="8dp"
+        android:nestedScrollingEnabled="true"
+        />
+
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index 5acb588..b3a3478 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -35,6 +35,7 @@
         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"
@@ -53,5 +54,6 @@
                   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 56815f8..039e89f 100644
--- a/core/res/res/layout/screen_toolbar.xml
+++ b/core/res/res/layout/screen_toolbar.xml
@@ -35,6 +35,7 @@
         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/values/attrs.xml b/core/res/res/values/attrs.xml
index 9438fcc..5fdadb7 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -985,6 +985,14 @@
 
         <!-- The color applied to framework buttons in their normal state. -->
         <attr name="colorButtonNormal" format="color" />
+
+        <!-- ================== -->
+        <!-- Hardware rendering -->
+        <!-- ================== -->
+        <eat-comment />
+
+        <!-- Reference to the Lighting style. -->
+        <attr name="lightingStyle" format="reference" />
     </declare-styleable>
 
     <!-- **************************************************************** -->
@@ -7141,4 +7149,13 @@
         <attr name="layout_ignoreOffset" format="boolean" />
         <attr name="layout_gravity" />
     </declare-styleable>
+
+    <!-- @hide -->
+    <declare-styleable name="Lighting">
+        <attr name="lightY" format="dimension" />
+        <attr name="lightZ" format="dimension" />
+        <attr name="lightRadius" format="dimension" />
+        <attr name="ambientShadowAlpha" format="float" />
+        <attr name="spotShadowAlpha" format="float" />
+    </declare-styleable>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4585b78..00f49a1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -835,6 +835,19 @@
          config_enableGeofenceOverlay is false. -->
     <string name="config_geofenceProviderPackageName" translatable="false">@null</string>
 
+    <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
+         Activity-Recognition to be replaced by an app at run-time. When disabled, only the
+         config_activityRecognitionHardwarePackageName package will be searched for
+         its implementation, otherwise packages whose signature matches the
+         signatures of config_locationProviderPackageNames will be searched, and
+         the service with the highest version number will be picked. Anyone who
+         wants to disable the overlay mechanism can set it to false.
+         -->
+    <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool>
+    <!-- Package name providing Hardware Activity-Recognition API support. Used only when
+         config_enableActivityRecognitionHardwareOverlay is false. -->
+    <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string>
+
     <!-- Package name(s) containing location provider support.
          These packages can contain services implementing location providers,
          such as the Geocode Provider, Network Location Provider, and
@@ -1621,4 +1634,6 @@
     the IMS connection -->
     <bool name="useImsAlwaysForEmergencyCall">true</bool>
 
+    <bool name="config_networkSamplingWakesDevice">true</bool>
+
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d2e023d..ad6c6cd7 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -365,4 +365,5 @@
     <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
     <dimen name="immersive_mode_cling_width">-1px</dimen>
 
+    <dimen name="resolver_max_width">480dp</dimen>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7c60c6e..5d57262 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2109,11 +2109,6 @@
     <string name="permdesc_modifyNetworkAccounting">Allows the app to modify how network usage is accounted against apps. Not for use by 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_markNetworkSocket">modify socket marks</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_markNetworkSocket">Allows the app to modify socket marks for routing</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_accessNotifications">access notifications</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_accessNotifications">Allows the app to retrieve, examine, and clear notifications, including those posted by other apps.</string>
@@ -3385,6 +3380,24 @@
 
     <!-- Title of intent resolver dialog when selecting an application to run. -->
     <string name="whichApplication">Complete action using</string>
+    <!-- Title of intent resolver dialog when selecting an application to run
+         and a previously used application is known. -->
+    <string name="whichApplicationNamed">Complete action using %1$s</string>
+    <!-- Title of intent resolver dialog when selecting a viewer application to run. -->
+    <string name="whichViewApplication">Open with</string>
+    <!-- Title of intent resolver dialog when selecting a viewer application to run
+         and a previously used application is known. -->
+    <string name="whichViewApplicationNamed">Open with %1$s</string>
+    <!-- Title of intent resolver dialog when selecting an editor application to run. -->
+    <string name="whichEditApplication">Edit with</string>
+    <!-- Title of intent resolver dialog when selecting an editor application to run
+         and a previously used application is known. -->
+    <string name="whichEditApplicationNamed">Edit with %1$s</string>
+    <!-- Title of intent resolver dialog when selecting a sharing application to run. -->
+    <string name="whichSendApplication">Share with</string>
+    <!-- Title of intent resolver dialog when selecting a sharing application to run
+         and a previously used application is known. -->
+    <string name="whichSendApplicationNamed">Share with %1$s</string>
     <!-- Title of intent resolver dialog when selecting a HOME application to run. -->
     <string name="whichHomeApplication">Select a home app</string>
     <!-- Option to always use the selected application resolution in the future. See the "Complete action using" dialog title-->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 7f216e5..0eceae6 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1206,6 +1206,7 @@
         <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">
@@ -1361,4 +1362,12 @@
         <item name="padding">16dp</item>
     </style>
 
+    <style name="Lighting">
+        <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>
+    </style>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cf0f7b7..cd7394a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -253,6 +253,7 @@
   <java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
   <java-symbol type="bool" name="config_enable_puk_unlock_screen" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
+  <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
   <java-symbol type="bool" name="config_sip_wifi_only" />
   <java-symbol type="bool" name="config_sms_capable" />
@@ -1477,6 +1478,7 @@
   <java-symbol type="bool" name="config_useAttentionLight" />
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
+  <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
   <java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
   <java-symbol type="bool" name="config_enableGeocoderOverlay" />
@@ -1569,6 +1571,7 @@
   <java-symbol type="string" name="car_mode_disable_notification_title" />
   <java-symbol type="string" name="chooser_wallpaper" />
   <java-symbol type="string" name="config_datause_iface" />
+  <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
   <java-symbol type="string" name="config_hardwareFlpPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
@@ -1901,8 +1904,21 @@
   <java-symbol type="attr" name="seekBarPreferenceStyle" />
   <java-symbol type="style" name="Theme.DeviceDefault.Resolver" />
   <java-symbol type="attr" name="preferenceFragmentStyle" />
-
   <java-symbol type="bool" name="skipHoldBeforeMerge" />
   <java-symbol type="bool" name="useImsAlwaysForEmergencyCall" />
   <java-symbol type="attr" name="touchscreenBlocksFocus" />
+  <java-symbol type="layout" name="resolver_list_with_default" />
+  <java-symbol type="string" name="whichApplicationNamed" />
+  <java-symbol type="string" name="whichViewApplication" />
+  <java-symbol type="string" name="whichViewApplicationNamed" />
+  <java-symbol type="string" name="whichEditApplication" />
+  <java-symbol type="string" name="whichEditApplicationNamed" />
+  <java-symbol type="string" name="whichSendApplication" />
+  <java-symbol type="string" name="whichSendApplicationNamed" />
+  <java-symbol type="attr" name="lightingStyle" />
+  <java-symbol type="attr" name="lightY" />
+  <java-symbol type="attr" name="lightZ" />
+  <java-symbol type="attr" name="lightRadius" />
+  <java-symbol type="attr" name="ambientShadowAlpha" />
+  <java-symbol type="attr" name="spotShadowAlpha" />
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d61253f..6ada975 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -420,6 +420,9 @@
 
         <!-- Accessibility focused drawable. -->
         <item name="accessibilityFocusedDrawable">@drawable/view_accessibility_focused</item>
+
+        <!-- Lighting and shadow style. -->
+        <item name="lightingStyle">@style/Lighting</item>
     </style>
 
     <!-- Variant of {@link #Theme} with no title bar -->
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index a9150c6..76bfc4b 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -942,11 +942,7 @@
 
     <!-- Holo theme for the TimePicker dialog windows, which is used by the
          {@link android.app.TimePickerDialog} class. -->
-    <style name="Theme.Holo.Dialog.TimePicker">
-        <item name="windowBackground">@color/transparent</item>
-        <item name="windowTitleStyle">@style/DialogWindowTitle.Holo</item>
-        <item name="windowContentOverlay">@null</item>
-    </style>
+    <style name="Theme.Holo.Dialog.TimePicker" parent="Theme.Holo.Dialog.Alert" />
 
     <!-- Theme for a window that will be displayed either full-screen on
          smaller screens (small, normal) or as a dialog on larger screens
@@ -1061,11 +1057,7 @@
 
     <!-- Holo Light theme for the TimePicker dialog windows, which is used by the
          {@link android.app.TimePickerDialog} class. -->
-    <style name="Theme.Holo.Light.Dialog.TimePicker">
-        <item name="windowBackground">@color/transparent</item>
-        <item name="windowTitleStyle">@style/DialogWindowTitle.Holo.Light</item>
-        <item name="windowContentOverlay">@null</item>
-    </style>
+    <style name="Theme.Holo.Light.Dialog.TimePicker" parent="Theme.Holo.Light.Dialog.Alert" />
 
     <!-- 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/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
index bf34f1d..e0a3a03 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
@@ -56,20 +56,20 @@
 
     @SmallTest
     public void testsetNameFilter() {
-        ScanFilter filter = mFilterBuilder.setName("Ped").build();
+        ScanFilter filter = mFilterBuilder.setDeviceName("Ped").build();
         assertTrue("setName filter fails", filter.matches(mScanResult));
 
-        filter = mFilterBuilder.setName("Pem").build();
+        filter = mFilterBuilder.setDeviceName("Pem").build();
         assertFalse("setName filter fails", filter.matches(mScanResult));
 
     }
 
     @SmallTest
     public void testDeviceFilter() {
-        ScanFilter filter = mFilterBuilder.setMacAddress(DEVICE_MAC).build();
+        ScanFilter filter = mFilterBuilder.setDeviceAddress(DEVICE_MAC).build();
         assertTrue("device filter fails", filter.matches(mScanResult));
 
-        filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build();
+        filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build();
         assertFalse("device filter fails", filter.matches(mScanResult));
     }
 
@@ -134,10 +134,10 @@
         ScanFilter filter = mFilterBuilder.build();
         testReadWriteParcelForFilter(filter);
 
-        filter = mFilterBuilder.setName("Ped").build();
+        filter = mFilterBuilder.setDeviceName("Ped").build();
         testReadWriteParcelForFilter(filter);
 
-        filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build();
+        filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build();
         testReadWriteParcelForFilter(filter);
 
         filter = mFilterBuilder.setServiceUuid(
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
index cece96b..e259bcc 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
@@ -51,7 +51,7 @@
         assertTrue(data.getServiceUuids().contains(uuid1));
         assertTrue(data.getServiceUuids().contains(uuid2));
 
-        assertEquals("Ped", data.getLocalName());
+        assertEquals("Ped", data.getDeviceName());
         assertEquals(-20, data.getTxPowerLevel());
 
         assertEquals(224, data.getManufacturerId());
diff --git a/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk b/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk
index a7dc1bd..60af2b9 100644
--- a/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk
+++ b/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk
@@ -34,6 +34,26 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
+LOCAL_MANIFEST_FILE := dual/AndroidManifest.xml
+
+LOCAL_SDK_VERSION := current
+include $(BUILD_PACKAGE)
+
+LOCAL_PATH:= $(TOP_LOCAL_PATH)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := PMTest_Java_multiarch
+LOCAL_MULTILIB := both
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MANIFEST_FILE := multiarch/AndroidManifest.xml
+
+LOCAL_JNI_SHARED_LIBRARIES = libpmtestdual
+
+LOCAL_PROGUARD_ENABLED := disabled
+
 LOCAL_SDK_VERSION := current
 include $(BUILD_PACKAGE)
 
diff --git a/core/tests/hosttests/test-apps/SharedUid/dual/AndroidManifest.xml b/core/tests/hosttests/test-apps/SharedUid/dual/dual/AndroidManifest.xml
similarity index 100%
rename from core/tests/hosttests/test-apps/SharedUid/dual/AndroidManifest.xml
rename to core/tests/hosttests/test-apps/SharedUid/dual/dual/AndroidManifest.xml
diff --git a/core/tests/hosttests/test-apps/SharedUid/dual/multiarch/AndroidManifest.xml b/core/tests/hosttests/test-apps/SharedUid/dual/multiarch/AndroidManifest.xml
new file mode 100644
index 0000000..d53ba50
--- /dev/null
+++ b/core/tests/hosttests/test-apps/SharedUid/dual/multiarch/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is an example of writing an application that bundles a
+     native code library. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.framework.shareduid.multiarch" android:sharedUserId="com.framework.shareduid">
+    <application android:label="Main Activity (multiarch)" android:multiArch="true">
+        <activity android:name="com.framework.shareduid.dual.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java
index 5bc36fa..323a360 100644
--- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java
+++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java
@@ -21,26 +21,59 @@
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
+import java.util.Objects;
+
 public class InputMethodSubtypeTest extends InstrumentationTestCase {
-    @SmallTest
-    public void testLocale() throws Exception {
-        // Compare locale
-        assertEquals(createDummySubtype("en_US").getLocale(),
-                createDummySubtype("en_US").getLocale());
-        assertEquals(createDummySubtype("en_US").getLocale(),
-                cloneViaParcel(createDummySubtype("en_US")).getLocale());
-        assertEquals(createDummySubtype("en_US").getLocale(),
-                cloneViaParcel(cloneViaParcel(createDummySubtype("en_US"))).getLocale());
+
+    public void verifyLocale(final String localeString) {
+        // InputMethodSubtype#getLocale() returns exactly the same string that is passed to the
+        // constructor.
+        assertEquals(localeString, createDummySubtype(localeString).getLocale());
+
+        // InputMethodSubtype#getLocale() should be preserved via marshaling.
+        assertEquals(createDummySubtype(localeString).getLocale(),
+                cloneViaParcel(createDummySubtype(localeString)).getLocale());
+
+        // InputMethodSubtype#getLocale() should be preserved via marshaling.
+        assertEquals(createDummySubtype(localeString).getLocale(),
+                cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).getLocale());
+
+        // Make sure InputMethodSubtype#hashCode() returns the same hash code.
+        assertEquals(createDummySubtype(localeString).hashCode(),
+                createDummySubtype(localeString).hashCode());
+        assertEquals(createDummySubtype(localeString).hashCode(),
+                cloneViaParcel(createDummySubtype(localeString)).hashCode());
+        assertEquals(createDummySubtype(localeString).hashCode(),
+                cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).hashCode());
     }
 
     @SmallTest
-    public void testHashCode() throws Exception {
-        assertEquals(createDummySubtype("en_US").hashCode(),
-                createDummySubtype("en_US").hashCode());
-        assertEquals(createDummySubtype("en_US").hashCode(),
-                cloneViaParcel(createDummySubtype("en_US")).hashCode());
-        assertEquals(createDummySubtype("en_US").hashCode(),
-                cloneViaParcel(cloneViaParcel(createDummySubtype("en_US"))).hashCode());
+    public void testLocaleString() throws Exception {
+        // The locale string in InputMethodSubtype has accepted an arbitrary text actually,
+        // regardless of the validity of the text as a locale string.
+        verifyLocale("en_US");
+        verifyLocale("apparently invalid locale string");
+        verifyLocale("zz");
+        verifyLocale("iw");
+        verifyLocale("he");
+    }
+
+    @SmallTest
+    public void testDeprecatedLocaleString() throws Exception {
+        // Make sure "iw" is not automatically replaced with "he".
+        final InputMethodSubtype subtypeIw = createDummySubtype("iw");
+        final InputMethodSubtype subtypeHe = createDummySubtype("he");
+        assertEquals("iw", subtypeIw.getLocale());
+        assertEquals("he", subtypeHe.getLocale());
+        assertFalse(Objects.equals(subtypeIw, subtypeHe));
+        assertFalse(Objects.equals(subtypeIw.hashCode(), subtypeHe.hashCode()));
+
+        final InputMethodSubtype clonedSubtypeIw = cloneViaParcel(subtypeIw);
+        final InputMethodSubtype clonedSubtypeHe = cloneViaParcel(subtypeHe);
+        assertEquals(subtypeIw, clonedSubtypeIw);
+        assertEquals(subtypeHe, clonedSubtypeHe);
+        assertEquals("iw", clonedSubtypeIw.getLocale());
+        assertEquals("he", clonedSubtypeHe.getLocale());
     }
 
     private static final InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) {
diff --git a/docs/html/images/ui/sample-linearlayout.png b/docs/html/images/ui/sample-linearlayout.png
index c4beb93a..04c9259 100644
--- a/docs/html/images/ui/sample-linearlayout.png
+++ b/docs/html/images/ui/sample-linearlayout.png
Binary files differ
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 6870ab4..936fe0c 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -69,12 +69,17 @@
 
     /**
      * Set the shader's local matrix. Passing null will reset the shader's
-     * matrix to identity
+     * matrix to identity.
+     *
+     * Starting with {@link android.os.Build.VERSION_CODES#L}, this does not
+     * modify any Paints which use this Shader. In order to modify the Paint,
+     * you need to call {@link Paint#setShader} again.
+     *
      * @param localM The shader's new local matrix, or null to specify identity
      */
     public void setLocalMatrix(Matrix localM) {
         mLocalMatrix = localM;
-        nativeSetLocalMatrix(native_instance,
+        native_instance = nativeSetLocalMatrix(native_instance,
                 localM == null ? 0 : localM.native_instance);
     }
 
@@ -109,6 +114,6 @@
     }
 
     private static native void nativeDestructor(long native_shader);
-    private static native void nativeSetLocalMatrix(long native_shader,
+    private static native long nativeSetLocalMatrix(long native_shader,
             long matrix_instance);
 }
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 83a8ed5..db5c8e3 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -467,10 +467,12 @@
             if (needMirroring()) {
                 updateMirrorMatrix(bounds.right - bounds.left);
                 shader.setLocalMatrix(mMirrorMatrix);
+                mBitmapState.mPaint.setShader(shader);
             } else {
                 if (mMirrorMatrix != null) {
                     mMirrorMatrix = null;
                     shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+                    mBitmapState.mPaint.setShader(shader);
                 }
             }
         }
@@ -547,10 +549,12 @@
                 // Mirror the bitmap
                 updateMirrorMatrix(mDstRect.right - mDstRect.left);
                 shader.setLocalMatrix(mMirrorMatrix);
+                paint.setShader(shader);
             } else {
                 if (mMirrorMatrix != null) {
                     mMirrorMatrix = null;
                     shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+                    paint.setShader(shader);
                 }
             }
 
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 87f5989..4f05313 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -153,25 +153,6 @@
     }
 
     /**
-     * @hide TO BE REMOVED FOR L-RELEASE
-     */
-    public RippleDrawable(Drawable content, Drawable mask) {
-        this(new RippleState(null, null, null), null, null);
-
-        if (content != null) {
-            addLayer(content, null, 0, 0, 0, 0, 0);
-        }
-
-        if (mask != null) {
-            addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
-        }
-
-        ensurePadding();
-
-        Log.e(LOG_TAG, "This constructor is being removed", new RuntimeException());
-    }
-
-    /**
      * Creates a new ripple drawable with the specified ripple color and
      * optional content and mask drawables.
      *
@@ -192,7 +173,7 @@
         }
 
         if (mask != null) {
-            addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
+            addLayer(mask, null, android.R.id.mask, 0, 0, 0, 0);
         }
 
         setColor(color);
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 7789358..ba5594a 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -306,7 +306,7 @@
 
 status_t DisplayListRenderer::drawArc(float left, float top, float right, float bottom,
         float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) {
-    if (fabs(sweepAngle) > 360.0f) {
+    if (fabs(sweepAngle) >= 360.0f) {
         return drawOval(left, top, right, bottom, paint);
     }
 
diff --git a/location/java/android/location/GpsClock.java b/location/java/android/location/GpsClock.java
new file mode 100644
index 0000000..f327c4e
--- /dev/null
+++ b/location/java/android/location/GpsClock.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class containing a GPS clock timestamp.
+ * It represents a measurement of the GPS receiver's clock.
+ *
+ * @hide
+ */
+public class GpsClock implements Parcelable {
+    // mandatory parameters
+    private long mTimeInNs;
+
+    // optional parameters
+    private boolean mHasLeapSecond;
+    private short mLeapSecond;
+    private boolean mHasTimeUncertaintyInNs;
+    private double mTimeUncertaintyInNs;
+    private boolean mHasBiasInNs;
+    private double mBiasInNs;
+    private boolean mHasBiasUncertaintyInNs;
+    private double mBiasUncertaintyInNs;
+    private boolean mHasDriftInNsPerSec;
+    private double mDriftInNsPerSec;
+    private boolean mHasDriftUncertaintyInNsPerSec;
+    private double mDriftUncertaintyInNsPerSec;
+
+    GpsClock() {
+        reset();
+    }
+
+    /**
+     * Sets all contents to the values stored in the provided object.
+     */
+    public void set(GpsClock clock) {
+        mTimeInNs = clock.mTimeInNs;
+
+        mHasLeapSecond = clock.mHasLeapSecond;
+        mLeapSecond = clock.mLeapSecond;
+        mHasTimeUncertaintyInNs = clock.mHasTimeUncertaintyInNs;
+        mTimeUncertaintyInNs = clock.mTimeUncertaintyInNs;
+        mHasBiasInNs = clock.mHasBiasInNs;
+        mBiasInNs = clock.mBiasInNs;
+        mHasBiasUncertaintyInNs = clock.mHasBiasUncertaintyInNs;
+        mBiasUncertaintyInNs = clock.mBiasUncertaintyInNs;
+        mHasDriftInNsPerSec = clock.mHasDriftInNsPerSec;
+        mDriftInNsPerSec = clock.mDriftInNsPerSec;
+        mHasDriftUncertaintyInNsPerSec = clock.mHasDriftUncertaintyInNsPerSec;
+        mDriftUncertaintyInNsPerSec = clock.mDriftUncertaintyInNsPerSec;
+    }
+
+    /**
+     * Resets all the contents to its original state.
+     */
+    public void reset() {
+        mTimeInNs = Long.MIN_VALUE;
+
+        resetLeapSecond();
+        resetTimeUncertaintyInNs();
+        resetBiasInNs();
+        resetBiasUncertaintyInNs();
+        resetDriftInNsPerSec();
+        resetDriftUncertaintyInNsPerSec();
+    }
+
+    /**
+     * Returns true if {@link #getLeapSecond()} is available, false otherwise.
+     */
+    public boolean hasLeapSecond() {
+        return mHasLeapSecond;
+    }
+
+    /**
+     * Gets the leap second associated with the clock's time.
+     *
+     * The value is only available if {@link #hasLeapSecond()} is true.
+     */
+    public short getLeapSecond() {
+        return mLeapSecond;
+    }
+
+    /**
+     * Sets the leap second associated with the clock's time.
+     */
+    public void setLeapSecond(short leapSecond) {
+        mHasLeapSecond = true;
+        mLeapSecond = leapSecond;
+    }
+
+    /**
+     * Resets the leap second associated with the clock's time.
+     */
+    public void resetLeapSecond() {
+        mHasLeapSecond = false;
+        mLeapSecond = Short.MIN_VALUE;
+    }
+
+    /**
+     * Gets the GPS clock Time in nanoseconds; it represents the uncorrected receiver's GPS time
+     * since 0000Z, January 6, 1980; this is, including {@link #getBiasInNs()}.
+     * The reported time includes {@link #getTimeUncertaintyInNs()}.
+     */
+    public long getTimeInNs() {
+        return mTimeInNs;
+    }
+
+    /**
+     * Sets the GPS clock Time in nanoseconds.
+     */
+    public void setTimeInNs(long timeInNs) {
+        mTimeInNs = timeInNs;
+    }
+
+    /**
+     * Returns true if {@link #getTimeUncertaintyInNs()} is available, false otherwise.
+     */
+    public boolean hasTimeUncertaintyInNs() {
+        return mHasTimeUncertaintyInNs;
+    }
+
+    /**
+     * Gets the clock's time Uncertainty (1-Sigma) in nanoseconds.
+     *
+     * The value is only available if {@link #hasTimeUncertaintyInNs()} is true.
+     */
+    public double getTimeUncertaintyInNs() {
+        return mTimeUncertaintyInNs;
+    }
+
+    /**
+     * Sets the clock's Time Uncertainty (1-Sigma) in nanoseconds.
+     */
+    public void setTimeUncertaintyInNs(double timeUncertaintyInNs) {
+        mHasTimeUncertaintyInNs = true;
+        mTimeUncertaintyInNs = timeUncertaintyInNs;
+    }
+
+    /**
+     * Resets the clock's Time Uncertainty (1-Sigma) in nanoseconds.
+     */
+    public void resetTimeUncertaintyInNs() {
+        mHasTimeUncertaintyInNs = false;
+        mTimeUncertaintyInNs = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getBiasInNs()} is available, false otherwise.
+     */
+    public boolean hasBiasInNs() {
+        return mHasBiasInNs;
+    }
+
+    /**
+     * Gets the clock's Bias in nanoseconds.
+     * The sign of the value (if available), is defined by the following equation:
+     *      true time = time - bias.
+     * The reported bias includes {@link #getBiasUncertaintyInNs()}.
+     *
+     * The value is only available if {@link #hasBiasInNs()} is true.
+     */
+    public Double getBiasInNs() {
+        return mBiasInNs;
+    }
+
+    /**
+     * Sets the clock's Bias in nanoseconds.
+     */
+    public void setBiasInNs(double biasInNs) {
+        mHasBiasInNs = true;
+        mBiasInNs = biasInNs;
+    }
+
+    /**
+     * Resets the clock's Bias in nanoseconds.
+     */
+    public void resetBiasInNs() {
+        mHasBiasInNs = false;
+        mBiasInNs = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getBiasUncertaintyInNs()} is available, false otherwise.
+     */
+    public boolean hasBiasUncertaintyInNs() {
+        return mHasBiasUncertaintyInNs;
+    }
+
+    /**
+     * Gets the clock's Bias Uncertainty (1-Sigma) in nanoseconds.
+     *
+     * The value is only available if {@link #hasBiasUncertaintyInNs()} is true.
+     */
+    public double getBiasUncertaintyInNs() {
+        return mBiasUncertaintyInNs;
+    }
+
+    /**
+     * Sets the clock's Bias Uncertainty (1-Sigma) in nanoseconds.
+     */
+    public void setBiasUncertaintyInNs(double biasUncertaintyInNs) {
+        mHasBiasUncertaintyInNs = true;
+        mBiasUncertaintyInNs = biasUncertaintyInNs;
+    }
+
+    /**
+     * Resets the clock's Bias Uncertainty (1-Sigma) in nanoseconds.
+     */
+    public void resetBiasUncertaintyInNs() {
+        mHasBiasUncertaintyInNs = false;
+        mBiasUncertaintyInNs = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getDriftInNsPerSec()} is available, false otherwise.
+     */
+    public boolean hasDriftInNsPerSec() {
+        return mHasDriftInNsPerSec;
+    }
+
+    /**
+     * Gets the clock's Drift in nanoseconds per second.
+     * A positive value indicates that the frequency is higher than the nominal frequency.
+     * The reported drift includes {@link #getDriftUncertaintyInNsPerSec()}.
+     *
+     * The value is only available if {@link #hasDriftInNsPerSec()} is true.
+     */
+    public double getDriftInNsPerSec() {
+        return mDriftInNsPerSec;
+    }
+
+    /**
+     * Sets the clock's Drift in nanoseconds per second.
+     */
+    public void setDriftInNsPerSec(double driftInNsPerSec) {
+        mHasDriftInNsPerSec = true;
+        mDriftInNsPerSec = driftInNsPerSec;
+    }
+
+    /**
+     * Resets the clock's Drift in nanoseconds per second.
+     */
+    public void resetDriftInNsPerSec() {
+        mHasDriftInNsPerSec = false;
+        mDriftInNsPerSec = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getDriftUncertaintyInNsPerSec()} is available, false otherwise.
+     */
+    public boolean hasDriftUncertaintyInNsPerSec() {
+        return mHasDriftUncertaintyInNsPerSec;
+    }
+
+    /**
+     * Gets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second.
+     *
+     * The value is only available if {@link #hasDriftUncertaintyInNsPerSec()} is true.
+     */
+    public double getDriftUncertaintyInNsPerSec() {
+        return mDriftUncertaintyInNsPerSec;
+    }
+
+    /**
+     * Sets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second.
+     */
+    public void setDriftUncertaintyInNsPerSec(double driftUncertaintyInNsPerSec) {
+        mHasDriftUncertaintyInNsPerSec = true;
+        mDriftUncertaintyInNsPerSec = driftUncertaintyInNsPerSec;
+    }
+
+    /**
+     * Resets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second.
+     */
+    public void resetDriftUncertaintyInNsPerSec() {
+        mHasDriftUncertaintyInNsPerSec = false;
+        mDriftUncertaintyInNsPerSec = Double.NaN;
+    }
+
+    public static final Creator<GpsClock> CREATOR = new Creator<GpsClock>() {
+        @Override
+        public GpsClock createFromParcel(Parcel parcel) {
+            GpsClock gpsClock = new GpsClock();
+            gpsClock.mTimeInNs = parcel.readLong();
+
+            gpsClock.mHasLeapSecond = parcel.readInt() != 0;
+            gpsClock.mLeapSecond = (short) parcel.readInt();
+            gpsClock.mHasTimeUncertaintyInNs = parcel.readInt() != 0;
+            gpsClock.mTimeUncertaintyInNs = parcel.readDouble();
+            gpsClock.mHasBiasInNs = parcel.readInt() != 0;
+            gpsClock.mBiasInNs = parcel.readDouble();
+            gpsClock.mHasBiasUncertaintyInNs = parcel.readInt() != 0;
+            gpsClock.mBiasUncertaintyInNs = parcel.readDouble();
+            gpsClock.mHasDriftInNsPerSec = parcel.readInt() != 0;
+            gpsClock.mDriftInNsPerSec = parcel.readDouble();
+            gpsClock.mHasDriftUncertaintyInNsPerSec = parcel.readInt() != 0;
+            gpsClock.mDriftUncertaintyInNsPerSec = parcel.readDouble();
+
+            return gpsClock;
+        }
+
+        @Override
+        public GpsClock[] newArray(int size) {
+            return new GpsClock[size];
+        }
+    };
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeLong(mTimeInNs);
+
+        parcel.writeInt(mHasLeapSecond ? 1 : 0);
+        parcel.writeInt(mLeapSecond);
+        parcel.writeInt(mHasTimeUncertaintyInNs ? 1 : 0);
+        parcel.writeDouble(mTimeUncertaintyInNs);
+        parcel.writeInt(mHasBiasInNs ? 1 : 0);
+        parcel.writeDouble(mBiasInNs);
+        parcel.writeInt(mHasBiasUncertaintyInNs ? 1 : 0);
+        parcel.writeDouble(mBiasUncertaintyInNs);
+        parcel.writeInt(mHasDriftInNsPerSec ? 1 : 0);
+        parcel.writeDouble(mDriftInNsPerSec);
+        parcel.writeInt(mHasDriftUncertaintyInNsPerSec ? 1 : 0);
+        parcel.writeDouble(mDriftUncertaintyInNsPerSec);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        final String format = "   %-15s = %-25s   %-26s = %s\n";
+        StringBuilder builder = new StringBuilder("GpsClock:\n");
+
+        builder.append(String.format(
+                format,
+                "LeapSecond",
+                mHasLeapSecond ? mLeapSecond : null,
+                "",
+                ""));
+
+        builder.append(String.format(
+                format,
+                "TimeInNs",
+                mTimeInNs,
+                "TimeUncertaintyInNs",
+                mHasTimeUncertaintyInNs ? mTimeUncertaintyInNs : null));
+
+        builder.append(String.format(
+                format,
+                "BiasInNs",
+                mHasBiasInNs ? mBiasInNs : null,
+                "BiasUncertaintyInNs",
+                mHasBiasUncertaintyInNs ? mBiasUncertaintyInNs : null));
+
+        builder.append(String.format(
+                format,
+                "DriftInNsPerSec",
+                mHasDriftInNsPerSec ? mDriftInNsPerSec : null,
+                "DriftUncertaintyInNsPerSec",
+                mHasDriftUncertaintyInNsPerSec ? mDriftUncertaintyInNsPerSec : null));
+
+        return builder.toString();
+    }
+}
diff --git a/location/java/android/location/GpsMeasurement.java b/location/java/android/location/GpsMeasurement.java
new file mode 100644
index 0000000..e5a8c8c
--- /dev/null
+++ b/location/java/android/location/GpsMeasurement.java
@@ -0,0 +1,1222 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A class representing a GPS satellite measurement, containing raw and computed information.
+ *
+ * @hide
+ */
+public class GpsMeasurement implements Parcelable {
+    private static final String TAG = "GpsMeasurement";
+
+    // mandatory parameters
+    private byte mPrn;
+    private long mLocalTimeInNs;
+    private long mReceivedGpsTowInNs;
+    private double mCn0InDbHz;
+    private double mPseudorangeRateInMetersPerSec;
+    private double mPseudorangeRateUncertaintyInMetersPerSec;
+    private double mAccumulatedDeltaRangeInMeters;
+    private double mAccumulatedDeltaRangeUncertaintyInMeters;
+
+    // optional parameters
+    private boolean mHasPseudorangeInMeters;
+    private double mPseudorangeInMeters;
+    private boolean mHasPseudorangeUncertaintyInMeters;
+    private double mPseudorangeUncertaintyInMeters;
+    private boolean mHasCodePhaseInChips;
+    private double mCodePhaseInChips;
+    private boolean mHasCodePhaseUncertaintyInChips;
+    private double mCodePhaseUncertaintyInChips;
+    private boolean mHasCarrierFrequencyInHz;
+    private float mCarrierFrequencyInHz;
+    private boolean mHasCarrierCycles;
+    private long mCarrierCycles;
+    private boolean mHasCarrierPhase;
+    private double mCarrierPhase;
+    private boolean mHasCarrierPhaseUncertainty;
+    private double mCarrierPhaseUncertainty;
+    private short mLossOfLock;
+    private boolean mHasBitNumber;
+    private short mBitNumber;
+    private boolean mHasTimeFromLastBitInNs;
+    private long mTimeFromLastBitInNs;
+    private boolean mHasDopplerShiftInHz;
+    private double mDopplerShiftInHz;
+    private boolean mHasDopplerShiftUncertaintyInHz;
+    private double mDopplerShiftUncertaintyInHz;
+    private short mMultipathIndicator;
+    private boolean mHasSnrInDb;
+    private double mSnrInDb;
+    private boolean mHasElevationInDeg;
+    private double mElevationInDeg;
+    private boolean mHasElevationUncertaintyInDeg;
+    private double mElevationUncertaintyInDeg;
+    private boolean mHasAzimuthInDeg;
+    private double mAzimuthInDeg;
+    private boolean mHasAzimuthUncertaintyInDeg;
+    private double mAzimuthUncertaintyInDeg;
+    private boolean mUsedInFix;
+
+    // The following enumerations must be in sync with the values declared in gps.h
+
+    /**
+     * The indicator is not available or it is unknown.
+     */
+    public static final short LOSS_OF_LOCK_UNKNOWN = 0;
+
+    /**
+     * The measurement does not present any indication of 'loss of lock'.
+     */
+    public static final short LOSS_OF_LOCK_OK = 1;
+
+    /**
+     * 'Loss of lock' detected between the previous and current observation: cycle slip possible.
+     */
+    public static final short LOSS_OF_LOCK_CYCLE_SLIP = 2;
+
+    /**
+     * The indicator is not available or it is unknown.
+     */
+    public static final short MULTIPATH_INDICATOR_UNKNOWN = 0;
+
+    /**
+     * The measurement has been indicated to use multi-path.
+     */
+    public static final short MULTIPATH_INDICATOR_DETECTED = 1;
+
+    /**
+     * The measurement has been indicated not tu use multi-path.
+     */
+    public static final short MULTIPATH_INDICATOR_NOT_USED = 2;
+
+    // End enumerations in sync with gps.h
+
+    GpsMeasurement() {
+        reset();
+    }
+
+    /**
+     * Sets all contents to the values stored in the provided object.
+     */
+    public void set(GpsMeasurement measurement) {
+        mPrn = measurement.mPrn;
+        mLocalTimeInNs = measurement.mLocalTimeInNs;
+        mReceivedGpsTowInNs = measurement.mReceivedGpsTowInNs;
+        mCn0InDbHz = measurement.mCn0InDbHz;
+        mPseudorangeRateInMetersPerSec = measurement.mPseudorangeRateInMetersPerSec;
+        mPseudorangeRateUncertaintyInMetersPerSec =
+                measurement.mPseudorangeRateUncertaintyInMetersPerSec;
+        mAccumulatedDeltaRangeInMeters = measurement.mAccumulatedDeltaRangeInMeters;
+        mAccumulatedDeltaRangeUncertaintyInMeters =
+                measurement.mAccumulatedDeltaRangeUncertaintyInMeters;
+
+        mHasPseudorangeInMeters = measurement.mHasPseudorangeInMeters;
+        mPseudorangeInMeters = measurement.mPseudorangeInMeters;
+        mHasPseudorangeUncertaintyInMeters = measurement.mHasPseudorangeUncertaintyInMeters;
+        mPseudorangeUncertaintyInMeters = measurement.mPseudorangeUncertaintyInMeters;
+        mHasCodePhaseInChips = measurement.mHasCodePhaseInChips;
+        mCodePhaseInChips = measurement.mCodePhaseInChips;
+        mHasCodePhaseUncertaintyInChips = measurement.mHasCodePhaseUncertaintyInChips;
+        mCodePhaseUncertaintyInChips = measurement.mCodePhaseUncertaintyInChips;
+        mHasCarrierFrequencyInHz = measurement.mHasCarrierFrequencyInHz;
+        mCarrierFrequencyInHz = measurement.mCarrierFrequencyInHz;
+        mHasCarrierCycles = measurement.mHasCarrierCycles;
+        mCarrierCycles = measurement.mCarrierCycles;
+        mHasCarrierPhase = measurement.mHasCarrierPhase;
+        mCarrierPhase = measurement.mCarrierPhase;
+        mHasCarrierPhaseUncertainty = measurement.mHasCarrierPhaseUncertainty;
+        mCarrierPhaseUncertainty = measurement.mCarrierPhaseUncertainty;
+        mLossOfLock = measurement.mLossOfLock;
+        mHasBitNumber = measurement.mHasBitNumber;
+        mBitNumber = measurement.mBitNumber;
+        mHasTimeFromLastBitInNs = measurement.mHasTimeFromLastBitInNs;
+        mTimeFromLastBitInNs = measurement.mTimeFromLastBitInNs;
+        mHasDopplerShiftInHz = measurement.mHasDopplerShiftInHz;
+        mDopplerShiftInHz = measurement.mDopplerShiftInHz;
+        mHasDopplerShiftUncertaintyInHz = measurement.mHasDopplerShiftUncertaintyInHz;
+        mDopplerShiftUncertaintyInHz = measurement.mDopplerShiftUncertaintyInHz;
+        mMultipathIndicator = measurement.mMultipathIndicator;
+        mHasSnrInDb = measurement.mHasSnrInDb;
+        mSnrInDb = measurement.mSnrInDb;
+        mHasElevationInDeg = measurement.mHasElevationInDeg;
+        mElevationInDeg = measurement.mElevationInDeg;
+        mHasElevationUncertaintyInDeg = measurement.mHasElevationUncertaintyInDeg;
+        mElevationUncertaintyInDeg = measurement.mElevationUncertaintyInDeg;
+        mHasAzimuthInDeg = measurement.mHasAzimuthInDeg;
+        mAzimuthInDeg = measurement.mAzimuthInDeg;
+        mHasAzimuthUncertaintyInDeg = measurement.mHasAzimuthUncertaintyInDeg;
+        mAzimuthUncertaintyInDeg = measurement.mAzimuthUncertaintyInDeg;
+        mUsedInFix = measurement.mUsedInFix;
+    }
+
+    /**
+     * Resets all the contents to its original state.
+     */
+    public void reset() {
+        mPrn = Byte.MIN_VALUE;
+        mLocalTimeInNs = Long.MIN_VALUE;
+        mReceivedGpsTowInNs = Long.MIN_VALUE;
+        mCn0InDbHz = Double.MIN_VALUE;
+        mPseudorangeRateInMetersPerSec = Double.MIN_VALUE;
+        mPseudorangeRateUncertaintyInMetersPerSec = Double.MIN_VALUE;
+        mAccumulatedDeltaRangeInMeters = Double.MIN_VALUE;
+        mAccumulatedDeltaRangeUncertaintyInMeters = Double.MIN_VALUE;
+
+        resetPseudorangeInMeters();
+        resetPseudorangeUncertaintyInMeters();
+        resetCodePhaseInChips();
+        resetCodePhaseUncertaintyInChips();
+        resetCarrierFrequencyInHz();
+        resetCarrierCycles();
+        resetCarrierPhase();
+        resetCarrierPhaseUncertainty();
+        setLossOfLock(LOSS_OF_LOCK_UNKNOWN);
+        resetBitNumber();
+        resetTimeFromLastBitInNs();
+        resetDopplerShiftInHz();
+        resetDopplerShiftUncertaintyInHz();
+        setMultipathIndicator(MULTIPATH_INDICATOR_UNKNOWN);
+        resetSnrInDb();
+        resetElevationInDeg();
+        resetElevationUncertaintyInDeg();
+        resetAzimuthInDeg();
+        resetAzimuthUncertaintyInDeg();
+        setUsedInFix(false);
+    }
+
+    /**
+     * Gets the Pseudo-random number (PRN).
+     * Range: [1, 32]
+     */
+    public byte getPrn() {
+        return mPrn;
+    }
+
+    /**
+     * Sets the Pseud-random number (PRN).
+     */
+    public void setPrn(byte value) {
+        mPrn = value;
+    }
+
+    /**
+     * Gets the local (hardware) time at which the measurement was taken in nanoseconds.
+     */
+    public long getLocalTimeInNs() {
+        return mLocalTimeInNs;
+    }
+
+    /**
+     * Sets the measurement's local (hardware) time in nanoseconds.
+     */
+    public void setLocalTimeInNs(long value) {
+        mLocalTimeInNs = value;
+    }
+
+    /**
+     * Gets the received GPS Time-of-Week in nanoseconds.
+     * The value is relative to the beginning of the current GPS week.
+     */
+    public long getReceivedGpsTowInNs() {
+        return mReceivedGpsTowInNs;
+    }
+
+    /**
+     * Sets the received GPS time-of-week in nanoseconds.
+     */
+    public void setReceivedGpsTowInNs(long value) {
+        mReceivedGpsTowInNs = value;
+    }
+
+    /**
+     * Gets the Carrier-to-noise density in dB-Hz.
+     * Range: [0, 63].
+     *
+     * The value contains the measured C/N0 for the signal at the antenna input.
+     */
+    public double getCn0InDbHz() {
+        return mCn0InDbHz;
+    }
+
+    /**
+     * Sets the carrier-to-noise density in dB-Hz.
+     */
+    public void setCn0InDbHz(double value) {
+        mCn0InDbHz = value;
+    }
+
+    /**
+     * Gets the Pseudorange rate at the timestamp in m/s.
+     * The reported value includes {@link #getPseudorangeRateUncertaintyInMetersPerSec()}.
+     */
+    public double getPseudorangeRateInMetersPerSec() {
+        return mPseudorangeRateInMetersPerSec;
+    }
+
+    /**
+     * Sets the pseudorange rate at the timestamp in m/s.
+     */
+    public void setPseudorangeRateInMetersPerSec(double value) {
+        mPseudorangeRateInMetersPerSec = value;
+    }
+
+    /**
+     * Gets the pseudorange's rate uncertainty (1-Sigma) in m/s.
+     * The uncertainty is represented as an absolute (single sided) value.
+     */
+    public double getPseudorangeRateUncertaintyInMetersPerSec() {
+        return mPseudorangeRateUncertaintyInMetersPerSec;
+    }
+
+    /**
+     * Sets the pseudorange's rate uncertainty (1-Sigma) in m/s.
+     */
+    public void setPseudorangeRateUncertaintyInMetersPerSec(double value) {
+        mPseudorangeRateUncertaintyInMetersPerSec = value;
+    }
+
+    /**
+     * Gets the accumulated delta range since the last channel reset, in meters.
+     * The reported value includes {@link #getAccumulatedDeltaRangeUncertaintyInMeters()}.
+     */
+    public double getAccumulatedDeltaRangeInMeters() {
+        return mAccumulatedDeltaRangeInMeters;
+    }
+
+    /**
+     * Sets the accumulated delta range in meters.
+     */
+    public void setAccumulatedDeltaRangeInMeters(double value) {
+        mAccumulatedDeltaRangeInMeters = value;
+    }
+
+    /**
+     * Gets the accumulated delta range's uncertainty (1-Sigma) in meters.
+     * The uncertainty is represented as an absolute (single sided) value.
+     */
+    public double getAccumulatedDeltaRangeUncertaintyInMeters() {
+        return mAccumulatedDeltaRangeUncertaintyInMeters;
+    }
+
+    /**
+     * Sets the accumulated delta range's uncertainty (1-sigma) in meters.
+     */
+    public void setAccumulatedDeltaRangeUncertaintyInMeters(double value) {
+        mAccumulatedDeltaRangeUncertaintyInMeters = value;
+    }
+
+    /**
+     * Returns true if {@link #getPseudorangeInMeters()} is available, false otherwise.
+     */
+    public boolean hasPseudorangeInMeters() {
+        return mHasPseudorangeInMeters;
+    }
+
+    /**
+     * Gets the best derived pseudorange by the chipset, in meters.
+     * The reported pseudorange includes {@link #getPseudorangeUncertaintyInMeters()}.
+     *
+     * The value is only available if {@link #hasPseudorangeInMeters()} is true.
+     */
+    public double getPseudorangeInMeters() {
+        return mPseudorangeInMeters;
+    }
+
+    /**
+     * Sets the Pseudo-range in meters.
+     */
+    public void setPseudorangeInMeters(double value) {
+        mHasPseudorangeInMeters = true;
+        mPseudorangeInMeters = value;
+    }
+
+    /**
+     * Resets the Pseudo-range in meters.
+     */
+    public void resetPseudorangeInMeters() {
+        mHasPseudorangeInMeters = false;
+        mPseudorangeInMeters = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getPseudorangeUncertaintyInMeters()} is available, false otherwise.
+     */
+    public boolean hasPseudorangeUncertaintyInMeters() {
+        return mHasPseudorangeUncertaintyInMeters;
+    }
+
+    /**
+     * Gets the pseudorange's uncertainty (1-Sigma) in meters.
+     * The value contains the 'pseudorange' and 'clock' uncertainty in it.
+     * The uncertainty is represented as an absolute (single sided) value.
+     *
+     * The value is only available if {@link #hasPseudorangeUncertaintyInMeters()} is true.
+     */
+    public double getPseudorangeUncertaintyInMeters() {
+        return mPseudorangeUncertaintyInMeters;
+    }
+
+    /**
+     * Sets the pseudo-range's uncertainty (1-Sigma) in meters.
+     */
+    public void setPseudorangeUncertaintyInMeters(double value) {
+        mHasPseudorangeUncertaintyInMeters = true;
+        mPseudorangeUncertaintyInMeters = value;
+    }
+
+    /**
+     * Resets the pseudo-range's uncertainty (1-Sigma) in meters.
+     */
+    public void resetPseudorangeUncertaintyInMeters() {
+        mHasPseudorangeUncertaintyInMeters = false;
+        mPseudorangeUncertaintyInMeters = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getCodePhaseInChips()} is available, false otherwise.
+     */
+    public boolean hasCodePhaseInChips() {
+        return mHasCodePhaseInChips;
+    }
+
+    /**
+     * Gets the fraction of the current C/A code cycle.
+     * Range: [0, 1023]
+     * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+     * The reported code-phase includes {@link #getCodePhaseUncertaintyInChips()}.
+     *
+     * The value is only available if {@link #hasCodePhaseInChips()} is true.
+     */
+    public double getCodePhaseInChips() {
+        return mCodePhaseInChips;
+    }
+
+    /**
+     * Sets the Code-phase in chips.
+     */
+    public void setCodePhaseInChips(double value) {
+        mHasCodePhaseInChips = true;
+        mCodePhaseInChips = value;
+    }
+
+    /**
+     * Resets the Code-phase in chips.
+     */
+    public void resetCodePhaseInChips() {
+        mHasCodePhaseInChips = false;
+        mCodePhaseInChips = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getCodePhaseUncertaintyInChips()} is available, false otherwise.
+     */
+    public boolean hasCodePhaseUncertaintyInChips() {
+        return mHasCodePhaseUncertaintyInChips;
+    }
+
+    /**
+     * Gets the code-phase's uncertainty (1-Sigma) as a fraction of chips.
+     * The uncertainty is represented as an absolute (single sided) value.
+     *
+     * The value is only available if {@link #hasCodePhaseUncertaintyInChips()} is true.
+     */
+    public double getCodePhaseUncertaintyInChips() {
+        return mCodePhaseUncertaintyInChips;
+    }
+
+    /**
+     * Sets the Code-phase's uncertainty (1-Sigma) in fractions of chips.
+     */
+    public void setCodePhaseUncertaintyInChips(double value) {
+        mHasCodePhaseUncertaintyInChips = true;
+        mCodePhaseUncertaintyInChips = value;
+    }
+
+    /**
+     * Resets the Code-phase's uncertainty (1-Sigma) in fractions of chips.
+     */
+    public void resetCodePhaseUncertaintyInChips() {
+        mHasCodePhaseUncertaintyInChips = false;
+        mCodePhaseUncertaintyInChips = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getCarrierFrequencyInHz()} is available, false otherwise.
+     */
+    public boolean hasCarrierFrequencyInHz() {
+        return mHasCarrierFrequencyInHz;
+    }
+
+    /**
+     * Gets the carrier frequency at which codes and messages are modulated, it can be L1 or L2.
+     * If the field is not set, the carrier frequency corresponds to L1.
+     *
+     * The value is only available if {@link #hasCarrierFrequencyInHz()} is true.
+     */
+    public float getCarrierFrequencyInHz() {
+        return mCarrierFrequencyInHz;
+    }
+
+    /**
+     * Sets the Carrier frequency (L1 or L2) in Hz.
+     */
+    public void setCarrierFrequencyInHz(float carrierFrequencyInHz) {
+        mHasCarrierFrequencyInHz = true;
+        mCarrierFrequencyInHz = carrierFrequencyInHz;
+    }
+
+    /**
+     * Resets the Carrier frequency (L1 or L2) in Hz.
+     */
+    public void resetCarrierFrequencyInHz() {
+        mHasCarrierFrequencyInHz = false;
+        mCarrierFrequencyInHz = Float.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getCarrierCycles()} is available, false otherwise.
+     */
+    public boolean hasCarrierCycles() {
+        return mHasCarrierCycles;
+    }
+
+    /**
+     * The number of full carrier cycles between the satellite and the receiver.
+     * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+     *
+     * The value is only available if {@link #hasCarrierCycles()} is true.
+     */
+    public long getCarrierCycles() {
+        return mCarrierCycles;
+    }
+
+    /**
+     * Sets the number of full carrier cycles between the satellite and the receiver.
+     */
+    public void setCarrierCycles(long value) {
+        mHasCarrierCycles = true;
+        mCarrierCycles = value;
+    }
+
+    /**
+     * Resets the number of full carrier cycles between the satellite and the receiver.
+     */
+    public void resetCarrierCycles() {
+        mHasCarrierCycles = false;
+        mCarrierCycles = Long.MIN_VALUE;
+    }
+
+    /**
+     * Returns true if {@link #getCarrierPhase()} is available, false otherwise.
+     */
+    public boolean hasCarrierPhase() {
+        return mHasCarrierPhase;
+    }
+
+    /**
+     * Gets the RF phase detected by the receiver.
+     * Range: [0.0, 1.0].
+     * This is usually the fractional part of the complete carrier phase measurement.
+     *
+     * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+     * The reported carrier-phase includes {@link #getCarrierPhaseUncertainty()}.
+     *
+     * The value is only available if {@link #hasCarrierPhase()} is true.
+     */
+    public double getCarrierPhase() {
+        return mCarrierPhase;
+    }
+
+    /**
+     * Sets the RF phase detected by the receiver.
+     */
+    public void setCarrierPhase(double value) {
+        mHasCarrierPhase = true;
+        mCarrierPhase = value;
+    }
+
+    /**
+     * Resets the RF phase detected by the receiver.
+     */
+    public void resetCarrierPhase() {
+        mHasCarrierPhase = false;
+        mCarrierPhase = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getCarrierPhaseUncertainty()} is available, false otherwise.
+     */
+    public boolean hasCarrierPhaseUncertainty() {
+        return mHasCarrierPhaseUncertainty;
+    }
+
+    /**
+     * Gets the carrier-phase's uncertainty (1-Sigma).
+     * The uncertainty is represented as an absolute (single sided) value.
+     *
+     * The value is only available if {@link #hasCarrierPhaseUncertainty()} is true.
+     */
+    public double getCarrierPhaseUncertainty() {
+        return mCarrierPhaseUncertainty;
+    }
+
+    /**
+     * Sets the Carrier-phase's uncertainty (1-Sigma) in cycles.
+     */
+    public void setCarrierPhaseUncertainty(double value) {
+        mHasCarrierPhaseUncertainty = true;
+        mCarrierPhaseUncertainty = value;
+    }
+
+    /**
+     * Resets the Carrier-phase's uncertainty (1-Sigma) in cycles.
+     */
+    public void resetCarrierPhaseUncertainty() {
+        mHasCarrierPhaseUncertainty = false;
+        mCarrierPhaseUncertainty = Double.NaN;
+    }
+
+    /**
+     * Gets a value indicating the 'loss of lock' state of the event.
+     */
+    public short getLossOfLock() {
+        return mLossOfLock;
+    }
+
+    /**
+     * Sets the 'loss of lock' status.
+     */
+    public void setLossOfLock(short value) {
+        switch (value) {
+            case LOSS_OF_LOCK_UNKNOWN:
+            case LOSS_OF_LOCK_OK:
+            case LOSS_OF_LOCK_CYCLE_SLIP:
+                mLossOfLock = value;
+                break;
+            default:
+                Log.d(TAG, "Sanitizing invalid 'loss of lock': " + value);
+                mLossOfLock = LOSS_OF_LOCK_UNKNOWN;
+                break;
+        }
+    }
+
+    /**
+     * Gets a string representation of the 'loss of lock'.
+     * For internal and logging use only.
+     */
+    private String getLossOfLockString() {
+        switch (mLossOfLock) {
+            case LOSS_OF_LOCK_UNKNOWN:
+                return "Unknown";
+            case LOSS_OF_LOCK_OK:
+                return "Ok";
+            case LOSS_OF_LOCK_CYCLE_SLIP:
+                return "CycleSlip";
+            default:
+                return "Invalid";
+        }
+    }
+
+    /**
+     * Returns true if {@link #getBitNumber()} is available, false otherwise.
+     */
+    public boolean hasBitNumber() {
+        return mHasBitNumber;
+    }
+
+    /**
+     * Gets the number of GPS bits transmitted since Sat-Sun midnight (GPS week).
+     *
+     * The value is only available if {@link #hasBitNumber()} is true.
+     */
+    public short getBitNumber() {
+        return mBitNumber;
+    }
+
+    /**
+     * Sets the bit number within the broadcast frame.
+     */
+    public void setBitNumber(short bitNumber) {
+        mHasBitNumber = true;
+        mBitNumber = bitNumber;
+    }
+
+    /**
+     * Resets the bit number within the broadcast frame.
+     */
+    public void resetBitNumber() {
+        mHasBitNumber = false;
+        mBitNumber = Short.MIN_VALUE;
+    }
+
+    /**
+     * Returns true if {@link #getTimeFromLastBitInNs()} is available, false otherwise.
+     */
+    public boolean hasTimeFromLastBitInNs() {
+        return mHasTimeFromLastBitInNs;
+    }
+
+    /**
+     * Gets the elapsed time since the last received bit in nanoseconds.
+     * Range: [0, 20000000].
+     *
+     * The value is only available if {@link #hasTimeFromLastBitInNs()} is true.
+     */
+    public long getTimeFromLastBitInNs() {
+        return mTimeFromLastBitInNs;
+    }
+
+    /**
+     * Sets the elapsed time since the last received bit in nanoseconds.
+     */
+    public void setTimeFromLastBitInNs(long value) {
+        mHasTimeFromLastBitInNs = true;
+        mTimeFromLastBitInNs = value;
+    }
+
+    /**
+     * Resets the elapsed time since the last received bit in nanoseconds.
+     */
+    public void resetTimeFromLastBitInNs() {
+        mHasTimeFromLastBitInNs = false;
+        mTimeFromLastBitInNs = Long.MIN_VALUE;
+    }
+
+    /**
+     * Returns true if {@link #getDopplerShiftInHz()} is available, false otherwise.
+     */
+    public boolean hasDopplerShiftInHz() {
+        return mHasDopplerShiftInHz;
+    }
+
+    /**
+     * Gets the Doppler Shift in Hz.
+     * A positive value indicates that the SV is moving toward the receiver.
+     *
+     * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+     * The reported doppler shift includes {@link #getDopplerShiftUncertaintyInHz()}.
+     *
+     * The value is only available if {@link #hasDopplerShiftInHz()} is true.
+     */
+    public double getDopplerShiftInHz() {
+        return mDopplerShiftInHz;
+    }
+
+    /**
+     * Sets the Doppler shift in Hz.
+     */
+    public void setDopplerShiftInHz(double value) {
+        mHasDopplerShiftInHz = true;
+        mDopplerShiftInHz = value;
+    }
+
+    /**
+     * Resets the Doppler shift in Hz.
+     */
+    public void resetDopplerShiftInHz() {
+        mHasDopplerShiftInHz = false;
+        mDopplerShiftInHz = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getDopplerShiftUncertaintyInHz()} is available, false otherwise.
+     */
+    public boolean hasDopplerShiftUncertaintyInHz() {
+        return mHasDopplerShiftUncertaintyInHz;
+    }
+
+    /**
+     * Gets the Doppler's Shift uncertainty (1-Sigma) in Hz.
+     * The uncertainty is represented as an absolute (single sided) value.
+     *
+     * The value is only available if {@link #hasDopplerShiftUncertaintyInHz()} is true.
+     */
+    public double getDopplerShiftUncertaintyInHz() {
+        return mDopplerShiftUncertaintyInHz;
+    }
+
+    /**
+     * Sets the Doppler's shift uncertainty (1-Sigma) in Hz.
+     */
+    public void setDopplerShiftUncertaintyInHz(double value) {
+        mHasDopplerShiftUncertaintyInHz = true;
+        mDopplerShiftUncertaintyInHz = value;
+    }
+
+    /**
+     * Resets the Doppler's shift uncertainty (1-Sigma) in Hz.
+     */
+    public void resetDopplerShiftUncertaintyInHz() {
+        mHasDopplerShiftUncertaintyInHz = false;
+        mDopplerShiftUncertaintyInHz = Double.NaN;
+    }
+
+    /**
+     * Gets a value indicating the 'multipath' state of the event.
+     */
+    public short getMultipathIndicator() {
+        return mMultipathIndicator;
+    }
+
+    /**
+     * Sets the 'multi-path' indicator.
+     */
+    public void setMultipathIndicator(short value) {
+        switch (value) {
+            case MULTIPATH_INDICATOR_UNKNOWN:
+            case MULTIPATH_INDICATOR_DETECTED:
+            case MULTIPATH_INDICATOR_NOT_USED:
+                mMultipathIndicator = value;
+                break;
+            default:
+                Log.d(TAG, "Sanitizing invalid 'muti-path indicator': " + value);
+                mMultipathIndicator = MULTIPATH_INDICATOR_UNKNOWN;
+                break;
+        }
+    }
+
+    /**
+     * Gets a string representation of the 'multi-path indicator'.
+     * For internal and logging use only.
+     */
+    private String getMultipathIndicatorString() {
+        switch(mMultipathIndicator) {
+            case MULTIPATH_INDICATOR_UNKNOWN:
+                return "Unknown";
+            case MULTIPATH_INDICATOR_DETECTED:
+                return "Detected";
+            case MULTIPATH_INDICATOR_NOT_USED:
+                return "NotDetected";
+            default:
+                return "Invalid";
+        }
+    }
+
+    /**
+     * Returns true if {@link #getSnrInDb()} is available, false otherwise.
+     */
+    public boolean hasSnrInDb() {
+        return mHasSnrInDb;
+    }
+
+    /**
+     * Gets the Signal-to-Noise ratio (SNR) in dB.
+     *
+     * The value is only available if {@link #hasSnrInDb()} is true.
+     */
+    public double getSnrInDb() {
+        return mSnrInDb;
+    }
+
+    /**
+     * Sets the Signal-to-noise ratio (SNR) in dB.
+     */
+    public void setSnrInDb(double snrInDb) {
+        mHasSnrInDb = true;
+        mSnrInDb = snrInDb;
+    }
+
+    /**
+     * Resets the Signal-to-noise ratio (SNR) in dB.
+     */
+    public void resetSnrInDb() {
+        mHasSnrInDb = false;
+        mSnrInDb = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getElevationInDeg()} is available, false otherwise.
+     */
+    public boolean hasElevationInDeg() {
+        return mHasElevationInDeg;
+    }
+
+    /**
+     * Gets the Elevation in degrees.
+     * Range: [-90, 90]
+     * The reported elevation includes {@link #getElevationUncertaintyInDeg()}.
+     *
+     * The value is only available if {@link #hasElevationInDeg()} is true.
+     */
+    public double getElevationInDeg() {
+        return mElevationInDeg;
+    }
+
+    /**
+     * Sets the Elevation in degrees.
+     */
+    public void setElevationInDeg(double elevationInDeg) {
+        mHasElevationInDeg = true;
+        mElevationInDeg = elevationInDeg;
+    }
+
+    /**
+     * Resets the Elevation in degrees.
+     */
+    public void resetElevationInDeg() {
+        mHasElevationInDeg = false;
+        mElevationInDeg = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getElevationUncertaintyInDeg()} is available, false otherwise.
+     */
+    public boolean hasElevationUncertaintyInDeg() {
+        return mHasElevationUncertaintyInDeg;
+    }
+
+    /**
+     * Gets the elevation's uncertainty (1-Sigma) in degrees.
+     * Range: [0, 90]
+     *
+     * The uncertainty is represented as an absolute (single sided) value.
+     *
+     * The value is only available if {@link #hasElevationUncertaintyInDeg()} is true.
+     */
+    public double getElevationUncertaintyInDeg() {
+        return mElevationUncertaintyInDeg;
+    }
+
+    /**
+     * Sets the elevation's uncertainty (1-Sigma) in degrees.
+     */
+    public void setElevationUncertaintyInDeg(double value) {
+        mHasElevationUncertaintyInDeg = true;
+        mElevationUncertaintyInDeg = value;
+    }
+
+    /**
+     * Resets the elevation's uncertainty (1-Sigma) in degrees.
+     */
+    public void resetElevationUncertaintyInDeg() {
+        mHasElevationUncertaintyInDeg = false;
+        mElevationUncertaintyInDeg = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getAzimuthInDeg()} is available, false otherwise.
+     */
+    public boolean hasAzimuthInDeg() {
+        return mHasAzimuthInDeg;
+    }
+
+    /**
+     * Gets the azimuth in degrees.
+     * Range: [0, 360).
+     *
+     * The reported azimuth includes {@link #getAzimuthUncertaintyInDeg()}.
+     *
+     * The value is only available if {@link #hasAzimuthInDeg()} is true.
+     */
+    public double getAzimuthInDeg() {
+        return mAzimuthInDeg;
+    }
+
+    /**
+     * Sets the Azimuth in degrees.
+     */
+    public void setAzimuthInDeg(double value) {
+        mHasAzimuthInDeg = true;
+        mAzimuthInDeg = value;
+    }
+
+    /**
+     * Resets the Azimuth in degrees.
+     */
+    public void resetAzimuthInDeg() {
+        mHasAzimuthInDeg = false;
+        mAzimuthInDeg = Double.NaN;
+    }
+
+    /**
+     * Returns true if {@link #getAzimuthUncertaintyInDeg()} is available, false otherwise.
+     */
+    public boolean hasAzimuthUncertaintyInDeg() {
+        return mHasAzimuthUncertaintyInDeg;
+    }
+
+    /**
+     * Gets the azimuth's uncertainty (1-Sigma) in degrees.
+     * Range: [0, 180].
+     *
+     * The uncertainty is represented as an absolute (single sided) value.
+     *
+     * The value is only available if {@link #hasAzimuthUncertaintyInDeg()} is true.
+     */
+    public double getAzimuthUncertaintyInDeg() {
+        return mAzimuthUncertaintyInDeg;
+    }
+
+    /**
+     * Sets the Azimuth's uncertainty (1-Sigma) in degrees.
+     */
+    public void setAzimuthUncertaintyInDeg(double value) {
+        mHasAzimuthUncertaintyInDeg = true;
+        mAzimuthUncertaintyInDeg = value;
+    }
+
+    /**
+     * Resets the Azimuth's uncertainty (1-Sigma) in degrees.
+     */
+    public void resetAzimuthUncertaintyInDeg() {
+        mHasAzimuthUncertaintyInDeg = false;
+        mAzimuthUncertaintyInDeg = Double.NaN;
+    }
+
+    /**
+     * Gets a flag indicating whether the GPS represented by the measurement was used for computing
+     * the most recent fix.
+     *
+     * @return A non-null value if the data is available, null otherwise.
+     */
+    public Boolean isUsedInFix() {
+        return mUsedInFix;
+    }
+
+    /**
+     * Sets the Used-in-Fix flag.
+     */
+    public void setUsedInFix(boolean value) {
+        mUsedInFix = value;
+    }
+
+    public static final Creator<GpsMeasurement> CREATOR = new Creator<GpsMeasurement>() {
+        @Override
+        public GpsMeasurement createFromParcel(Parcel parcel) {
+            GpsMeasurement gpsMeasurement = new GpsMeasurement();
+
+            gpsMeasurement.mPrn = parcel.readByte();
+            gpsMeasurement.mLocalTimeInNs = parcel.readLong();
+            gpsMeasurement.mReceivedGpsTowInNs = parcel.readLong();
+            gpsMeasurement.mCn0InDbHz = parcel.readDouble();
+            gpsMeasurement.mPseudorangeRateInMetersPerSec = parcel.readDouble();
+            gpsMeasurement.mPseudorangeRateUncertaintyInMetersPerSec = parcel.readDouble();
+            gpsMeasurement.mAccumulatedDeltaRangeInMeters = parcel.readDouble();
+            gpsMeasurement.mAccumulatedDeltaRangeUncertaintyInMeters = parcel.readDouble();
+
+            gpsMeasurement.mHasPseudorangeInMeters = parcel.readInt() != 0;
+            gpsMeasurement.mPseudorangeInMeters = parcel.readDouble();
+            gpsMeasurement.mHasPseudorangeUncertaintyInMeters = parcel.readInt() != 0;
+            gpsMeasurement.mPseudorangeUncertaintyInMeters = parcel.readDouble();
+            gpsMeasurement.mHasCodePhaseInChips = parcel.readInt() != 0;
+            gpsMeasurement.mCodePhaseInChips = parcel.readDouble();
+            gpsMeasurement.mHasCodePhaseUncertaintyInChips = parcel.readInt() != 0;
+            gpsMeasurement.mCodePhaseUncertaintyInChips = parcel.readDouble();
+            gpsMeasurement.mHasCarrierFrequencyInHz = parcel.readInt() != 0;
+            gpsMeasurement.mCarrierFrequencyInHz = parcel.readFloat();
+            gpsMeasurement.mHasCarrierCycles = parcel.readInt() != 0;
+            gpsMeasurement.mCarrierCycles = parcel.readLong();
+            gpsMeasurement.mHasCarrierPhase = parcel.readInt() != 0;
+            gpsMeasurement.mCarrierPhase = parcel.readDouble();
+            gpsMeasurement.mHasCarrierPhaseUncertainty = parcel.readInt() != 0;
+            gpsMeasurement.mCarrierPhaseUncertainty = parcel.readDouble();
+            gpsMeasurement.mLossOfLock = (short) parcel.readInt();
+            gpsMeasurement.mHasBitNumber = parcel.readInt() != 0;
+            gpsMeasurement.mBitNumber = (short) parcel.readInt();
+            gpsMeasurement.mHasTimeFromLastBitInNs = parcel.readInt() != 0;
+            gpsMeasurement.mTimeFromLastBitInNs = parcel.readLong();
+            gpsMeasurement.mHasDopplerShiftInHz = parcel.readInt() != 0;
+            gpsMeasurement.mDopplerShiftInHz = parcel.readDouble();
+            gpsMeasurement.mHasDopplerShiftUncertaintyInHz = parcel.readInt() != 0;
+            gpsMeasurement.mDopplerShiftUncertaintyInHz = parcel.readDouble();
+            gpsMeasurement.mMultipathIndicator = (short) parcel.readInt();
+            gpsMeasurement.mHasSnrInDb = parcel.readInt() != 0;
+            gpsMeasurement.mSnrInDb = parcel.readDouble();
+            gpsMeasurement.mHasElevationInDeg = parcel.readInt() != 0;
+            gpsMeasurement.mElevationInDeg = parcel.readDouble();
+            gpsMeasurement.mHasElevationUncertaintyInDeg = parcel.readInt() != 0;
+            gpsMeasurement.mElevationUncertaintyInDeg = parcel.readDouble();
+            gpsMeasurement.mHasAzimuthInDeg = parcel.readInt() != 0;
+            gpsMeasurement.mAzimuthInDeg = parcel.readDouble();
+            gpsMeasurement.mHasAzimuthUncertaintyInDeg = parcel.readInt() != 0;
+            gpsMeasurement.mAzimuthUncertaintyInDeg = parcel.readDouble();
+            gpsMeasurement.mUsedInFix = parcel.readInt() != 0;
+
+            return gpsMeasurement;
+        }
+
+        @Override
+        public GpsMeasurement[] newArray(int i) {
+            return new GpsMeasurement[i];
+        }
+    };
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeByte(mPrn);
+        parcel.writeLong(mLocalTimeInNs);
+        parcel.writeLong(mReceivedGpsTowInNs);
+        parcel.writeDouble(mCn0InDbHz);
+        parcel.writeDouble(mPseudorangeRateInMetersPerSec);
+        parcel.writeDouble(mPseudorangeRateUncertaintyInMetersPerSec);
+        parcel.writeDouble(mAccumulatedDeltaRangeInMeters);
+        parcel.writeDouble(mAccumulatedDeltaRangeUncertaintyInMeters);
+
+        parcel.writeInt(mHasPseudorangeInMeters ? 1 : 0);
+        parcel.writeDouble(mPseudorangeInMeters);
+        parcel.writeInt(mHasPseudorangeUncertaintyInMeters ? 1 : 0);
+        parcel.writeDouble(mPseudorangeUncertaintyInMeters);
+        parcel.writeInt(mHasCodePhaseInChips ? 1 : 0);
+        parcel.writeDouble(mCodePhaseInChips);
+        parcel.writeInt(mHasCodePhaseUncertaintyInChips ? 1 : 0);
+        parcel.writeDouble(mCodePhaseUncertaintyInChips);
+        parcel.writeInt(mHasCarrierFrequencyInHz ? 1 : 0);
+        parcel.writeFloat(mCarrierFrequencyInHz);
+        parcel.writeInt(mHasCarrierCycles ? 1 : 0);
+        parcel.writeLong(mCarrierCycles);
+        parcel.writeInt(mHasCarrierPhase ? 1 : 0);
+        parcel.writeDouble(mCarrierPhase);
+        parcel.writeInt(mHasCarrierPhaseUncertainty ? 1 : 0);
+        parcel.writeDouble(mCarrierPhaseUncertainty);
+        parcel.writeInt(mLossOfLock);
+        parcel.writeInt(mHasBitNumber ? 1 : 0);
+        parcel.writeInt(mBitNumber);
+        parcel.writeInt(mHasTimeFromLastBitInNs ? 1 : 0);
+        parcel.writeLong(mTimeFromLastBitInNs);
+        parcel.writeInt(mHasDopplerShiftInHz ? 1 : 0);
+        parcel.writeDouble(mDopplerShiftInHz);
+        parcel.writeInt(mHasDopplerShiftUncertaintyInHz ? 1 : 0);
+        parcel.writeDouble(mDopplerShiftUncertaintyInHz);
+        parcel.writeInt(mMultipathIndicator);
+        parcel.writeInt(mHasSnrInDb ? 1 : 0);
+        parcel.writeDouble(mSnrInDb);
+        parcel.writeInt(mHasElevationInDeg ? 1 : 0);
+        parcel.writeDouble(mElevationInDeg);
+        parcel.writeInt(mHasElevationUncertaintyInDeg ? 1 : 0);
+        parcel.writeDouble(mElevationUncertaintyInDeg);
+        parcel.writeInt(mHasAzimuthInDeg ? 1 : 0);
+        parcel.writeDouble(mAzimuthInDeg);
+        parcel.writeInt(mHasAzimuthUncertaintyInDeg ? 1 : 0);
+        parcel.writeDouble(mAzimuthUncertaintyInDeg);
+        parcel.writeInt(mUsedInFix ? 1 : 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        final String format = "   %-29s = %s\n";
+        final String formatWithUncertainty = "   %-29s = %-25s   %-40s = %s\n";
+        StringBuilder builder = new StringBuilder("GpsMeasurement:\n");
+
+        builder.append(String.format(format, "Prn", mPrn));
+
+        builder.append(String.format(format, "LocalTimeInNs", mLocalTimeInNs));
+
+        builder.append(String.format(format, "ReceivedGpsTowInNs", mReceivedGpsTowInNs));
+
+        builder.append(String.format(format, "Cn0InDbHz", mCn0InDbHz));
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "PseudorangeRateInMetersPerSec",
+                mPseudorangeRateInMetersPerSec,
+                "PseudorangeRateUncertaintyInMetersPerSec",
+                mPseudorangeRateUncertaintyInMetersPerSec));
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "AccumulatedDeltaRangeInMeters",
+                mAccumulatedDeltaRangeInMeters,
+                "AccumulatedDeltaRangeUncertaintyInMeters",
+                mAccumulatedDeltaRangeUncertaintyInMeters));
+
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "PseudorangeInMeters",
+                mHasPseudorangeInMeters ? mPseudorangeInMeters : null,
+                "PseudorangeUncertaintyInMeters",
+                mHasPseudorangeUncertaintyInMeters ? mPseudorangeUncertaintyInMeters : null));
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "CodePhaseInChips",
+                mHasCodePhaseInChips ? mCodePhaseInChips : null,
+                "CodePhaseUncertaintyInChips",
+                mHasCodePhaseUncertaintyInChips ? mCodePhaseUncertaintyInChips : null));
+
+        builder.append(String.format(
+                format,
+                "CarrierFrequencyInHz",
+                mHasCarrierFrequencyInHz ? mCarrierFrequencyInHz : null));
+
+        builder.append(String.format(
+                format,
+                "CarrierCycles",
+                mHasCarrierCycles ? mCarrierCycles : null));
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "CarrierPhase",
+                mHasCarrierPhase ? mCarrierPhase : null,
+                "CarrierPhaseUncertainty",
+                mHasCarrierPhaseUncertainty ? mCarrierPhaseUncertainty : null));
+
+        builder.append(String.format(format, "LossOfLock", getLossOfLockString()));
+
+        builder.append(String.format(
+                format,
+                "BitNumber",
+                mHasBitNumber ? mBitNumber : null));
+
+        builder.append(String.format(
+                format,
+                "TimeFromLastBitInNs",
+                mHasTimeFromLastBitInNs ? mTimeFromLastBitInNs : null));
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "DopplerShiftInHz",
+                mHasDopplerShiftInHz ? mDopplerShiftInHz : null,
+                "DopplerShiftUncertaintyInHz",
+                mHasDopplerShiftUncertaintyInHz ? mDopplerShiftUncertaintyInHz : null));
+
+        builder.append(String.format(format, "MultipathIndicator", getMultipathIndicatorString()));
+
+        builder.append(String.format(
+                format,
+                "SnrInDb",
+                mHasSnrInDb ? mSnrInDb : null));
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "ElevationInDeg",
+                mHasElevationInDeg ? mElevationInDeg : null,
+                "ElevationUncertaintyInDeg",
+                mHasElevationUncertaintyInDeg ? mElevationUncertaintyInDeg : null));
+
+        builder.append(String.format(
+                formatWithUncertainty,
+                "AzimuthInDeg",
+                mHasAzimuthInDeg ? mAzimuthInDeg : null,
+                "AzimuthUncertaintyInDeg",
+                mHasAzimuthUncertaintyInDeg ? mAzimuthUncertaintyInDeg : null));
+
+        builder.append(String.format(format, "UsedInFix", mUsedInFix));
+
+        return builder.toString();
+    }
+}
diff --git a/location/java/android/location/GpsMeasurementListenerTransport.java b/location/java/android/location/GpsMeasurementListenerTransport.java
new file mode 100644
index 0000000..48a4b44
--- /dev/null
+++ b/location/java/android/location/GpsMeasurementListenerTransport.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.location;
+
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A handler class to manage transport listeners for {@link GpsMeasurementsEvent.Listener},
+ * and post the events in a handler.
+ *
+ * @hide
+ */
+class GpsMeasurementListenerTransport {
+    private static final String TAG = "GpsMeasurementListenerTransport";
+
+    private final Context mContext;
+    private final ILocationManager mLocationManager;
+
+    private final IGpsMeasurementsListener mListenerTransport = new ListenerTransport();
+    private final HashSet<GpsMeasurementsEvent.Listener> mListeners =
+            new HashSet<GpsMeasurementsEvent.Listener>();
+
+    public GpsMeasurementListenerTransport(Context context, ILocationManager locationManager) {
+        mContext = context;
+        mLocationManager = locationManager;
+    }
+
+    public boolean add(@NonNull GpsMeasurementsEvent.Listener listener) {
+        Preconditions.checkNotNull(listener);
+
+        synchronized (mListeners) {
+            // we need to register with the service first, because we need to find out if the
+            // service will actually support the request before we attempt anything
+            if (mListeners.isEmpty()) {
+                boolean registeredWithServer;
+                try {
+                    registeredWithServer = mLocationManager.addGpsMeasurementsListener(
+                            mListenerTransport,
+                            mContext.getPackageName());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error handling first listener.", e);
+                    return false;
+                }
+
+                if (!registeredWithServer) {
+                    Log.e(TAG, "Unable to register listener transport.");
+                    return false;
+                }
+            }
+
+            if (mListeners.contains(listener)) {
+                return true;
+            }
+
+            mListeners.add(listener);
+        }
+
+        return true;
+    }
+
+    public void remove(@NonNull GpsMeasurementsEvent.Listener listener) {
+        Preconditions.checkNotNull(listener);
+
+        synchronized (mListeners) {
+            boolean removed = mListeners.remove(listener);
+
+            boolean isLastListener = removed && mListeners.isEmpty();
+            if (isLastListener) {
+                try {
+                    mLocationManager.removeGpsMeasurementsListener(mListenerTransport);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error handling last listener.", e);
+                }
+            }
+        }
+    }
+
+    private class ListenerTransport extends IGpsMeasurementsListener.Stub {
+        @Override
+        public void onGpsMeasurementsReceived(final GpsMeasurementsEvent eventArgs) {
+            Collection<GpsMeasurementsEvent.Listener> listeners;
+            synchronized (mListeners) {
+                listeners = new ArrayList<GpsMeasurementsEvent.Listener>(mListeners);
+            }
+
+            for (final GpsMeasurementsEvent.Listener listener : listeners) {
+                listener.onGpsMeasurementsReceived(eventArgs);
+            }
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/location/java/android/location/GpsMeasurementsEvent.aidl
similarity index 75%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to location/java/android/location/GpsMeasurementsEvent.aidl
index 3da1321..2c46262 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/location/java/android/location/GpsMeasurementsEvent.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * 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 +14,6 @@
  * limitations under the License.
  */
 
-package android.bluetooth.le;
+package android.location;
 
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable GpsMeasurementsEvent;
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
new file mode 100644
index 0000000..e04ed81
--- /dev/null
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * A class implementing a container for data associated with a measurement event.
+ * Events are delivered to registered instances of {@link Listener}.
+ *
+ * @hide
+ */
+public class GpsMeasurementsEvent implements Parcelable {
+    private final GpsClock mClock;
+    private final Collection<GpsMeasurement> mReadOnlyMeasurements;
+
+    /**
+     * Used for receiving GPS satellite measurements from the GPS engine.
+     * Each measurement contains raw and computed data identifying a satellite.
+     * You can implement this interface and call {@link LocationManager#addGpsMeasurementListener}.
+     *
+     * @hide
+     */
+    public interface Listener {
+        void onGpsMeasurementsReceived(GpsMeasurementsEvent eventArgs);
+    }
+
+    public GpsMeasurementsEvent(GpsClock clock, GpsMeasurement[] measurements) {
+        if (clock == null) {
+            throw new InvalidParameterException("Parameter 'clock' must not be null.");
+        }
+        if (measurements == null || measurements.length == 0) {
+            throw new InvalidParameterException(
+                    "Parameter 'measurements' must not be null or empty.");
+        }
+
+        mClock = clock;
+        Collection<GpsMeasurement> measurementCollection = Arrays.asList(measurements);
+        mReadOnlyMeasurements = Collections.unmodifiableCollection(measurementCollection);
+    }
+
+    @NonNull
+    public GpsClock getClock() {
+        return mClock;
+    }
+
+    /**
+     * Gets a read-only collection of measurements associated with the current event.
+     */
+    @NonNull
+    public Collection<GpsMeasurement> getMeasurements() {
+        return mReadOnlyMeasurements;
+    }
+
+    public static final Creator<GpsMeasurementsEvent> CREATOR =
+            new Creator<GpsMeasurementsEvent>() {
+        @Override
+        public GpsMeasurementsEvent createFromParcel(Parcel in) {
+            ClassLoader classLoader = getClass().getClassLoader();
+
+            GpsClock clock = in.readParcelable(classLoader);
+
+            int measurementsLength = in.readInt();
+            GpsMeasurement[] measurementsArray = new GpsMeasurement[measurementsLength];
+            in.readTypedArray(measurementsArray, GpsMeasurement.CREATOR);
+
+            return new GpsMeasurementsEvent(clock, measurementsArray);
+        }
+
+        @Override
+        public GpsMeasurementsEvent[] newArray(int size) {
+            return new GpsMeasurementsEvent[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mClock, flags);
+
+        GpsMeasurement[] measurementsArray = mReadOnlyMeasurements.toArray(new GpsMeasurement[0]);
+        parcel.writeInt(measurementsArray.length);
+        parcel.writeTypedArray(measurementsArray, flags);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder("[ GpsMeasurementsEvent:\n\n");
+
+        builder.append(mClock.toString());
+        builder.append("\n");
+
+        for (GpsMeasurement measurement : mReadOnlyMeasurements) {
+            builder.append(measurement.toString());
+            builder.append("\n");
+        }
+
+        builder.append("]");
+
+        return builder.toString();
+    }
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/location/java/android/location/IGpsMeasurementsListener.aidl
similarity index 61%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to location/java/android/location/IGpsMeasurementsListener.aidl
index 3da1321..b34bb6c 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/location/java/android/location/IGpsMeasurementsListener.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * 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 +14,13 @@
  * limitations under the License.
  */
 
-package android.bluetooth.le;
+package android.location;
 
-parcelable AdvertisementData;
\ No newline at end of file
+import android.location.GpsMeasurementsEvent;
+
+/**
+ * {@hide}
+ */
+oneway interface IGpsMeasurementsListener {
+    void onGpsMeasurementsReceived(in GpsMeasurementsEvent event);
+}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index c353ec6..a1acaf1 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -21,7 +21,7 @@
 import android.location.Criteria;
 import android.location.GeocoderParams;
 import android.location.Geofence;
-import android.location.IGeocodeProvider;
+import android.location.IGpsMeasurementsListener;
 import android.location.IGpsStatusListener;
 import android.location.ILocationListener;
 import android.location.Location;
@@ -60,6 +60,9 @@
 
     boolean sendNiResponse(int notifId, int userResponse);
 
+    boolean addGpsMeasurementsListener(in IGpsMeasurementsListener listener, in String packageName);
+    boolean removeGpsMeasurementsListener(in IGpsMeasurementsListener listener);
+
     // --- deprecated ---
     List<String> getAllProviders();
     List<String> getProviders(in Criteria criteria, boolean enabledOnly);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 4502a5b..d6a8fb8 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -16,25 +16,23 @@
 
 package android.location;
 
+import com.android.internal.location.ProviderProperties;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.util.Log;
 
-
-import java.lang.SecurityException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
-import com.android.internal.location.ProviderProperties;
-
 /**
  * This class provides access to the system location services.  These
  * services allow applications to obtain periodic updates of the
@@ -60,6 +58,7 @@
 
     private final Context mContext;
     private final ILocationManager mService;
+    private final GpsMeasurementListenerTransport mGpsMeasurementListenerTransport;
     private final HashMap<GpsStatus.Listener, GpsStatusListenerTransport> mGpsStatusListeners =
             new HashMap<GpsStatus.Listener, GpsStatusListenerTransport>();
     private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =
@@ -310,6 +309,7 @@
     public LocationManager(Context context, ILocationManager service) {
         mService = service;
         mContext = context;
+        mGpsMeasurementListenerTransport = new GpsMeasurementListenerTransport(mContext, mService);
     }
 
     private LocationProvider createProvider(String name, ProviderProperties properties) {
@@ -1570,6 +1570,29 @@
         }
     }
 
+    /**
+     * Adds a GPS Measurement listener.
+     *
+     * @param listener a {@link android.location.GpsMeasurementsEvent.Listener} object to register.
+     * @return {@code true} if the listener was successfully registered, {@code false} otherwise.
+     *
+     * @hide
+     */
+    public boolean addGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {
+        return mGpsMeasurementListenerTransport.add(listener);
+    }
+
+    /**
+     * Removes a GPS Measurement listener.
+     *
+     * @param listener a {@link GpsMeasurementsEvent.Listener} object to remove.
+     *
+     * @hide
+     */
+    public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {
+        mGpsMeasurementListenerTransport.remove(listener);
+    }
+
      /**
      * Retrieves information about the current status of the GPS engine.
      * This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged}
diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
new file mode 100644
index 0000000..8707a10
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location.provider;
+
+import android.annotation.NonNull;
+
+import java.security.InvalidParameterException;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ */
+public class ActivityChangedEvent {
+    private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+    public ActivityChangedEvent(List<ActivityRecognitionEvent> activityRecognitionEvents) {
+        if (activityRecognitionEvents == null) {
+            throw new InvalidParameterException(
+                    "Parameter 'activityRecognitionEvents' must not be null.");
+        }
+
+        mActivityRecognitionEvents = activityRecognitionEvents;
+    }
+
+    @NonNull
+    public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+        return mActivityRecognitionEvents;
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..8c719ce4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.location.provider;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ */
+public class ActivityRecognitionEvent {
+    private final String mActivity;
+    private final int mEventType;
+    private final long mTimestampNs;
+
+    public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+        mActivity = activity;
+        mEventType = eventType;
+        mTimestampNs = timestampNs;
+    }
+
+    public String getActivity() {
+        return mActivity;
+    }
+
+    public int getEventType() {
+        return mEventType;
+    }
+
+    public long getTimestampNs() {
+        return mTimestampNs;
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
new file mode 100644
index 0000000..da33464
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import com.android.internal.util.Preconditions;
+
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareSink;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A class that exposes {@link IActivityRecognitionHardware} functionality to unbundled services.
+ */
+public final class ActivityRecognitionProvider {
+    private final IActivityRecognitionHardware mService;
+    private final HashSet<Sink> mSinkSet = new HashSet<Sink>();
+    private final SinkTransport mSinkTransport = new SinkTransport();
+
+    // the following constants must remain in sync with activity_recognition.h
+
+    public static final String ACTIVITY_IN_VEHICLE = "android.activity_recognition.in_vehicle";
+    public static final String ACTIVITY_ON_BICYCLE = "android.activity_recognition.on_bicycle";
+    public static final String ACTIVITY_WALKING = "android.activity_recognition.walking";
+    public static final String ACTIVITY_RUNNING = "android.activity_recognition.running";
+    public static final String ACTIVITY_STILL = "android.activity_recognition.still";
+    public static final String ACTIVITY_TILTING = "android.activity_recognition.tilting";
+
+    public static final int EVENT_TYPE_FLUSH_COMPLETE = 0;
+    public static final int EVENT_TYPE_ENTER = 1;
+    public static final int EVENT_TYPE_EXIT = 2;
+
+    // end constants activity_recognition.h
+
+    /**
+     * Used to receive Activity-Recognition events.
+     */
+    public interface Sink {
+        void onActivityChanged(ActivityChangedEvent event);
+    }
+
+    public ActivityRecognitionProvider(IActivityRecognitionHardware service)
+            throws RemoteException {
+        Preconditions.checkNotNull(service);
+        mService = service;
+        mService.registerSink(mSinkTransport);
+    }
+
+    public String[] getSupportedActivities() throws RemoteException {
+        return mService.getSupportedActivities();
+    }
+
+    public boolean isActivitySupported(String activity) throws RemoteException {
+        return mService.isActivitySupported(activity);
+    }
+
+    public void registerSink(Sink sink) {
+        Preconditions.checkNotNull(sink);
+        synchronized (mSinkSet) {
+            mSinkSet.add(sink);
+        }
+    }
+
+    // TODO: if this functionality is exposed to 3rd party developers, handle unregistration (here
+    // and in the service) of all sinks while failing to disable all events
+    public void unregisterSink(Sink sink) {
+        Preconditions.checkNotNull(sink);
+        synchronized (mSinkSet) {
+            mSinkSet.remove(sink);
+        }
+    }
+
+    public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs)
+            throws RemoteException {
+        return mService.enableActivityEvent(activity, eventType, reportLatencyNs);
+    }
+
+    public boolean disableActivityEvent(String activity, int eventType) throws RemoteException {
+        return mService.disableActivityEvent(activity, eventType);
+    }
+
+    public boolean flush() throws RemoteException {
+        return mService.flush();
+    }
+
+    private final class SinkTransport extends IActivityRecognitionHardwareSink.Stub {
+        @Override
+        public void onActivityChanged(
+                android.hardware.location.ActivityChangedEvent activityChangedEvent) {
+            Collection<Sink> sinks;
+            synchronized (mSinkSet) {
+                if (mSinkSet.isEmpty()) {
+                    return;
+                }
+
+                sinks = new ArrayList<Sink>(mSinkSet);
+            }
+
+            // translate the event from platform internal and GmsCore types
+            ArrayList<ActivityRecognitionEvent> gmsEvents =
+                    new ArrayList<ActivityRecognitionEvent>();
+            for (android.hardware.location.ActivityRecognitionEvent event
+                    : activityChangedEvent.getActivityRecognitionEvents()) {
+                ActivityRecognitionEvent gmsEvent = new ActivityRecognitionEvent(
+                        event.getActivity(),
+                        event.getEventType(),
+                        event.getTimestampNs());
+                gmsEvents.add(gmsEvent);
+            }
+            ActivityChangedEvent gmsEvent = new ActivityChangedEvent(gmsEvents);
+
+            for (Sink sink : sinks) {
+                sink.onActivityChanged(gmsEvent);
+            }
+        }
+    }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
new file mode 100644
index 0000000..03dd042
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A watcher class for Activity-Recognition instances.
+ */
+public class ActivityRecognitionProviderWatcher {
+    private static final String TAG = "ActivityRecognitionProviderWatcher";
+
+    private static ActivityRecognitionProviderWatcher sWatcher;
+    private static final Object sWatcherLock = new Object();
+
+    private ActivityRecognitionProvider mActivityRecognitionProvider;
+
+    private ActivityRecognitionProviderWatcher() {}
+
+    public static ActivityRecognitionProviderWatcher getInstance() {
+        synchronized (sWatcherLock) {
+            if (sWatcher == null) {
+                sWatcher = new ActivityRecognitionProviderWatcher();
+            }
+            return sWatcher;
+        }
+    }
+
+    private IActivityRecognitionHardwareWatcher.Stub mWatcherStub =
+            new IActivityRecognitionHardwareWatcher.Stub() {
+        @Override
+        public void onInstanceChanged(IActivityRecognitionHardware instance) {
+            int callingUid = Binder.getCallingUid();
+            if (callingUid != Process.SYSTEM_UID) {
+                Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+                return;
+            }
+
+            try {
+                mActivityRecognitionProvider = new ActivityRecognitionProvider(instance);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error creating Hardware Activity-Recognition", e);
+            }
+        }
+    };
+
+    /**
+     * Gets the binder needed to interact with proxy provider in the platform.
+     */
+    @NonNull
+    public IBinder getBinder() {
+        return mWatcherStub;
+    }
+
+    /**
+     * Gets an object that supports the functionality of {@link ActivityRecognitionProvider}.
+     *
+     * @return Non-null value if the functionality is supported by the platform, false otherwise.
+     */
+    @Nullable
+    public ActivityRecognitionProvider getActivityRecognitionProvider() {
+        return mActivityRecognitionProvider;
+    }
+}
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index d553d10..7a30920 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -412,16 +412,6 @@
         }
     };
 
-    /** @hide */
-    @Override
-    public String toString () {
-        return new String("AudioAttributes:"
-                + " usage=" + mUsage
-                + " content=" + mContentType
-                + " flags=0x" + Integer.toHexString(mFlags)
-                + " tags=" + mTags);
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -486,6 +476,53 @@
         }
     };
 
+    /** @hide */
+    @Override
+    public String toString () {
+        return new String("AudioAttributes:"
+                + " usage=" + mUsage
+                + " content=" + mContentType
+                + " flags=0x" + Integer.toHexString(mFlags).toUpperCase()
+                + " tags=" + mTags);
+    }
+
+    /** @hide */
+    public String usageToString() {
+        switch(mUsage) {
+            case USAGE_UNKNOWN:
+                return new String("USAGE_UNKNOWN");
+            case USAGE_MEDIA:
+                return new String("USAGE_MEDIA");
+            case USAGE_VOICE_COMMUNICATION:
+                return new String("USAGE_VOICE_COMMUNICATION");
+            case USAGE_VOICE_COMMUNICATION_SIGNALLING:
+                return new String("USAGE_VOICE_COMMUNICATION");
+            case USAGE_ALARM:
+                return new String("USAGE_ALARM");
+            case USAGE_NOTIFICATION:
+                return new String("USAGE_NOTIFICATION");
+            case USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
+                return new String("USAGE_NOTIFICATION");
+            case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+                return new String("USAGE_NOTIFICATION");
+            case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+                return new String("USAGE_NOTIFICATION_COMMUNICATION_INSTANT");
+            case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+                return new String("USAGE_NOTIFICATION_COMMUNICATION_DELAYED");
+            case USAGE_NOTIFICATION_EVENT:
+                return new String("USAGE_NOTIFICATION_EVENT");
+            case USAGE_ASSISTANCE_ACCESSIBILITY:
+                return new String("USAGE_ASSISTANCE_ACCESSIBILITY");
+            case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+                return new String("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE");
+            case USAGE_ASSISTANCE_SONIFICATION:
+                return new String("USAGE_ASSISTANCE_SONIFICATION");
+            case USAGE_GAME:
+                return new String("USAGE_GAME");
+            default:
+                return new String("unknown usage " + mUsage);
+        }
+    }
 
     /** @hide */
     @IntDef({
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index a471e83..025d354 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -227,19 +227,23 @@
     private int mChannelMask;
     private int mPropertySetMask;
 
-    int getEncoding() {
+    /** @hide */
+    public int getEncoding() {
         return mEncoding;
     }
 
-    int getSampleRate() {
+    /** @hide */
+    public int getSampleRate() {
         return mSampleRate;
     }
 
-    int getChannelMask() {
+    /** @hide */
+    public int getChannelMask() {
         return mChannelMask;
     }
 
-    int getPropertySetMask() {
+    /** @hide */
+    public int getPropertySetMask() {
         return mPropertySetMask;
     }
 
@@ -248,7 +252,7 @@
      * Builder class for {@link AudioFormat} objects.
      */
     public static class Builder {
-        private int mEncoding = ENCODING_DEFAULT;
+        private int mEncoding = ENCODING_PCM_16BIT;
         private int mSampleRate = 0;
         private int mChannelMask = CHANNEL_INVALID;
         private int mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_NONE;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c088b82..eb6bf2c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -25,6 +25,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.media.RemoteController.OnClientUpdateListener;
+import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioPolicyConfig;
 import android.media.session.MediaSessionLegacyHelper;
 import android.os.Binder;
 import android.os.Handler;
@@ -2449,6 +2451,52 @@
     }
 
     /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Register the given {@link AudioPolicy}.
+     * This call is synchronous and blocks until the registration process successfully completed
+     * or failed to complete.
+     * @param policy the {@link AudioPolicy} to register.
+     * @return {@link #ERROR} if there was an error communicating with the registration service
+     *    or if the user doesn't have the required
+     *    {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission,
+     *    {@link #SUCCESS} otherwise.
+     */
+    public int registerAudioPolicy(AudioPolicy policy) {
+        if (policy == null) {
+            throw new IllegalArgumentException("Illegal null AudioPolicy argument");
+        }
+        IAudioService service = getService();
+        try {
+            if (!service.registerAudioPolicy(policy.getConfig(), policy.token())) {
+                return ERROR;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in registerAudioPolicyAsync()", e);
+            return ERROR;
+        }
+        return SUCCESS;
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * @param policy the {@link AudioPolicy} to unregister.
+     */
+    public void unregisterAudioPolicyAsync(AudioPolicy policy) {
+        if (policy == null) {
+            throw new IllegalArgumentException("Illegal null AudioPolicy argument");
+        }
+        IAudioService service = getService();
+        try {
+            service.unregisterAudioPolicyAsync(policy.token());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
+        }
+    }
+
+
+    /**
      *  @hide
      *  Reload audio settings. This method is called by Settings backup
      *  agent when audio settings are restored and causes the AudioService
@@ -2912,6 +2960,7 @@
      */
 
     /** @hide
+     * CANDIDATE FOR PUBLIC API
      */
     public static final int SUCCESS = AudioSystem.SUCCESS;
     /**
@@ -2919,6 +2968,7 @@
      */
     public static final int ERROR = AudioSystem.ERROR;
     /** @hide
+     * CANDIDATE FOR PUBLIC API
      */
     public static final int ERROR_BAD_VALUE = AudioSystem.BAD_VALUE;
     /** @hide
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index a58a20f3..b08d631 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -48,6 +48,7 @@
 import android.hardware.usb.UsbManager;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
+import android.media.audiopolicy.AudioPolicyConfig;
 import android.media.session.MediaSessionLegacyHelper;
 import android.os.Binder;
 import android.os.Build;
@@ -68,6 +69,7 @@
 import android.telecomm.TelecommManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.Surface;
 import android.view.WindowManager;
@@ -5041,4 +5043,65 @@
             }
         }
     }
+
+    //==========================================================================================
+    // Audio policy management
+    //==========================================================================================
+    public boolean registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
+        //Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
+        boolean hasPermissionForPolicy =
+                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                        android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+        if (!hasPermissionForPolicy) {
+            Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
+                    + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
+            return false;
+        }
+        synchronized (mAudioPolicies) {
+            AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
+            try {
+                cb.linkToDeath(app, 0/*flags*/);
+                mAudioPolicies.put(cb, app);
+            } catch (RemoteException e) {
+                // audio policy owner has already died!
+                Slog.w(TAG, "Audio policy registration failed, could not link to " + cb +
+                        " binder death", e);
+                return false;
+            }
+        }
+        // TODO implement registration with native audio policy (including permission check)
+        return true;
+    }
+    public void unregisterAudioPolicyAsync(IBinder cb) {
+        synchronized (mAudioPolicies) {
+            AudioPolicyProxy app = mAudioPolicies.remove(cb);
+            if (app == null) {
+                Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
+                        + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
+            } else {
+                cb.unlinkToDeath(app, 0/*flags*/);
+            }
+        }
+        // TODO implement registration with native audio policy
+    }
+
+    public class AudioPolicyProxy implements IBinder.DeathRecipient {
+        private static final String TAG = "AudioPolicyProxy";
+        AudioPolicyConfig mConfig;
+        IBinder mToken;
+        AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
+            mConfig = config;
+            mToken = token;
+        }
+
+        public void binderDied() {
+            synchronized (mAudioPolicies) {
+                Log.v(TAG, "audio policy " + mToken + " died");
+                mAudioPolicies.remove(mToken);
+            }
+        }
+    };
+
+    private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
+            new HashMap<IBinder, AudioPolicyProxy>();
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 9bf7ace8..e112a65 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -28,6 +28,7 @@
 import android.media.IRingtonePlayer;
 import android.media.IVolumeController;
 import android.media.Rating;
+import android.media.audiopolicy.AudioPolicyConfig;
 import android.net.Uri;
 import android.view.KeyEvent;
 
@@ -199,4 +200,7 @@
     void disableSafeMediaVolume();
 
     int setHdmiSystemAudioSupported(boolean on, int device, String name);
+
+           boolean registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
+    oneway void unregisterAudioPolicyAsync(in IBinder cb);
 }
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index a346e17..2856edb 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -19,6 +19,8 @@
 import java.nio.ByteBuffer;
 import java.lang.AutoCloseable;
 
+import android.graphics.Rect;
+
 /**
  * <p>A single complete image buffer to use with a media source such as a
  * {@link MediaCodec} or a
@@ -121,6 +123,34 @@
      */
     public abstract long getTimestamp();
 
+    protected Rect mCropRect;
+
+    /**
+     * Get the crop rectangle associated with this frame.
+     * <p>
+     * The crop rectangle specifies the region of valid pixels in the image,
+     * using coordinates in the largest-resolution plane.
+     */
+    public Rect getCropRect() {
+        if (mCropRect == null) {
+            return new Rect(0, 0, getWidth(), getHeight());
+        } else {
+            return new Rect(mCropRect); // return a copy
+        }
+    }
+
+    /**
+     * Set the crop rectangle associated with this frame.
+     * <p>
+     * The crop rectangle specifies the region of valid pixels in the image,
+     * using coordinates in the largest-resolution plane.
+     */
+    public void setCropRect(Rect cropRect) {
+        cropRect = new Rect(cropRect);  // make a copy
+        cropRect.intersect(0, 0, getWidth(), getHeight());
+        mCropRect = cropRect;
+    }
+
     /**
      * Get the array of pixel planes for this Image. The number of planes is
      * determined by the format of the Image.
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index e9a5f3f..cb9776a 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.media.Image;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
 import android.media.MediaCrypto;
@@ -40,11 +41,15 @@
  * MediaCodec codec = MediaCodec.createDecoderByType(type);
  * codec.configure(format, ...);
  * codec.start();
+ *
+ * // if API level <= 20, get input and output buffer arrays here
  * ByteBuffer[] inputBuffers = codec.getInputBuffers();
  * ByteBuffer[] outputBuffers = codec.getOutputBuffers();
  * for (;;) {
  *   int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
  *   if (inputBufferIndex &gt;= 0) {
+ *     // if API level >= 21, get input buffer here
+ *     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
  *     // fill inputBuffers[inputBufferIndex] with valid data
  *     ...
  *     codec.queueInputBuffer(inputBufferIndex, ...);
@@ -52,13 +57,17 @@
  *
  *   int outputBufferIndex = codec.dequeueOutputBuffer(timeoutUs);
  *   if (outputBufferIndex &gt;= 0) {
+ *     // if API level >= 21, get output buffer here
+ *     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
  *     // outputBuffer is ready to be processed or rendered.
  *     ...
  *     codec.releaseOutputBuffer(outputBufferIndex, ...);
  *   } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ *     // no needed to handle if API level >= 21 and using getOutputBuffer(int)
  *     outputBuffers = codec.getOutputBuffers();
  *   } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
  *     // Subsequent data will conform to new format.
+ *     // can ignore if API level >= 21 and using getOutputFormat(outputBufferIndex)
  *     MediaFormat format = codec.getOutputFormat();
  *     ...
  *   }
@@ -70,9 +79,11 @@
  *
  * Each codec maintains a number of input and output buffers that are
  * referred to by index in API calls.
+ * <p>
+ * For API levels 20 and below:
  * The contents of these buffers are represented by the ByteBuffer[] arrays
  * accessible through {@link #getInputBuffers} and {@link #getOutputBuffers}.
- *
+ * <p>
  * After a successful call to {@link #start} the client "owns" neither
  * input nor output buffers, subsequent calls to {@link #dequeueInputBuffer}
  * and {@link #dequeueOutputBuffer} then transfer ownership from the codec
@@ -101,19 +112,19 @@
  * first few buffers submitted to the codec object after starting it must
  * be codec specific data marked as such using the flag {@link #BUFFER_FLAG_CODEC_CONFIG}
  * in a call to {@link #queueInputBuffer}.
- *
+ * <p>
  * Codec specific data included in the format passed to {@link #configure}
  * (in ByteBuffer entries with keys "csd-0", "csd-1", ...) is automatically
  * submitted to the codec, this data MUST NOT be submitted explicitly by the
  * client.
- *
+ * <p>
  * Once the client reaches the end of the input data it signals the end of
  * the input stream by specifying a flag of {@link #BUFFER_FLAG_END_OF_STREAM} in the call to
  * {@link #queueInputBuffer}. The codec will continue to return output buffers
  * until it eventually signals the end of the output stream by specifying
  * the same flag ({@link #BUFFER_FLAG_END_OF_STREAM}) on the BufferInfo returned in
  * {@link #dequeueOutputBuffer}.
- *
+ * <p>
  * In order to start decoding data that's not adjacent to previously submitted
  * data (i.e. after a seek) it is necessary to {@link #flush} the decoder.
  * Any input or output buffers the client may own at the point of the flush are
@@ -166,9 +177,19 @@
 final public class MediaCodec {
     /**
      * Per buffer metadata includes an offset and size specifying
-     * the range of valid data in the associated codec buffer.
+     * the range of valid data in the associated codec (output) buffer.
      */
     public final static class BufferInfo {
+        /**
+         * Update the buffer metadata information.
+         *
+         * @param newOffset the start-offset of the data in the buffer.
+         * @param newSize   the amount of data (in bytes) in the buffer.
+         * @param newTimeUs the presentation timestamp in microseconds.
+         * @param newFlags  buffer flags associated with the buffer.  This
+         * should be a combination of  {@link #BUFFER_FLAG_KEY_FRAME} and
+         * {@link #BUFFER_FLAG_END_OF_STREAM}.
+         */
         public void set(
                 int newOffset, int newSize, long newTimeUs, int newFlags) {
             offset = newOffset;
@@ -177,9 +198,39 @@
             flags = newFlags;
         }
 
+        /**
+         * The start-offset of the data in the buffer.
+         */
         public int offset;
+
+        /**
+         * The amount of data (in bytes) in the buffer.  If this is {@code 0},
+         * the buffer has no data in it and can be discarded.  The only
+         * use of a 0-size buffer is to carry the end-of-stream marker.
+         */
         public int size;
+
+        /**
+         * The presentation timestamp in microseconds for the buffer.
+         * This is derived from the presentation timestamp passed in
+         * with the corresponding input buffer.  This should be ignored for
+         * a 0-sized buffer.
+         */
         public long presentationTimeUs;
+
+        /**
+         * Buffer flags associated with the buffer.  A combination of
+         * {@link #BUFFER_FLAG_KEY_FRAME} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+         *
+         * <p>Encoded buffers that are key frames are marked with
+         * {@link #BUFFER_FLAG_KEY_FRAME}.
+         *
+         * <p>The last output buffer corresponding to the input buffer
+         * marked with {@link #BUFFER_FLAG_END_OF_STREAM} will also be marked
+         * with {@link #BUFFER_FLAG_END_OF_STREAM}. In some cases this could
+         * be an empty buffer, whose sole purpose is to carry the end-of-stream
+         * marker.
+         */
         public int flags;
     };
 
@@ -187,10 +238,18 @@
     // in MediaCodec.h !
 
     /**
-     * This indicates that the buffer marked as such contains the data
-     * for a sync frame.
+     * This indicates that the (encoded) buffer marked as such contains
+     * the data for a key frame.
+     *
+     * @deprecated Use {@link #BUFFER_FLAG_KEY_FRAME} instead.
      */
-    public static final int BUFFER_FLAG_SYNC_FRAME   = 1;
+    public static final int BUFFER_FLAG_SYNC_FRAME = 1;
+
+    /**
+     * This indicates that the (encoded) buffer marked as such contains
+     * the data for a key frame.
+     */
+    public static final int BUFFER_FLAG_KEY_FRAME = 1;
 
     /**
      * This indicated that the buffer marked as such contains codec
@@ -202,12 +261,18 @@
      * This signals the end of stream, i.e. no buffers will be available
      * after this, unless of course, {@link #flush} follows.
      */
-    public static final int BUFFER_FLAG_END_OF_STREAM         = 4;
+    public static final int BUFFER_FLAG_END_OF_STREAM = 4;
 
     private EventHandler mEventHandler;
-    private volatile NotificationCallback mNotificationCallback;
+    private Callback mCallback;
 
-    static final int EVENT_NOTIFY = 1;
+    private static final int EVENT_CALLBACK = 1;
+    private static final int EVENT_SET_CALLBACK = 2;
+
+    private static final int CB_INPUT_AVAILABLE = 1;
+    private static final int CB_OUTPUT_AVAILABLE = 2;
+    private static final int CB_ERROR = 3;
+    private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
 
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
@@ -220,12 +285,66 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case EVENT_NOTIFY:
+                case EVENT_CALLBACK:
                 {
-                    NotificationCallback cb = mNotificationCallback;
-                    if (cb != null) {
-                        cb.onCodecNotify(mCodec);
+                    handleCallback(msg);
+                    break;
+                }
+                case EVENT_SET_CALLBACK:
+                {
+                    mCallback = (MediaCodec.Callback) msg.obj;
+                    break;
+                }
+                default:
+                {
+                    break;
+                }
+            }
+        }
+
+        private void handleCallback(Message msg) {
+            if (mCallback == null) {
+                return;
+            }
+
+            switch (msg.arg1) {
+                case CB_INPUT_AVAILABLE:
+                {
+                    int index = msg.arg2;
+                    synchronized(mBufferLock) {
+                        validateInputByteBuffer(mCachedInputBuffers, index);
                     }
+                    mCallback.onInputBufferAvailable(mCodec, index);
+                    break;
+                }
+
+                case CB_OUTPUT_AVAILABLE:
+                {
+                    int index = msg.arg2;
+                    BufferInfo info = (MediaCodec.BufferInfo) msg.obj;
+                    synchronized(mBufferLock) {
+                        validateOutputByteBuffer(mCachedOutputBuffers, index, info);
+                    }
+                    mCallback.onOutputBufferAvailable(
+                            mCodec, index, info);
+                    break;
+                }
+
+                case CB_ERROR:
+                {
+                    mCallback.onError(mCodec, (MediaCodec.CodecException) msg.obj);
+                    break;
+                }
+
+                case CB_OUTPUT_FORMAT_CHANGE:
+                {
+                    mCallback.onOutputFormatChanged(mCodec,
+                            new MediaFormat((Map<String, Object>) msg.obj));
+                    break;
+                }
+
+                default:
+                {
                     break;
                 }
             }
@@ -299,6 +418,7 @@
         } else {
             mEventHandler = null;
         }
+        mBufferLock = new Object();
 
         native_setup(name, nameIsType, encoder);
     }
@@ -313,7 +433,12 @@
      * component instance instead of relying on the garbage collector
      * to do this for you at some point in the future.
      */
-    public native final void release();
+    public final void release() {
+        freeAllTrackedBuffers(); // free buffers first
+        native_release();
+    }
+
+    private native final void native_release();
 
     /**
      * If this codec is to be used as an encoder, pass this flag.
@@ -360,6 +485,8 @@
         native_configure(keys, values, surface, crypto, flags);
     }
 
+    private native final void native_setCallback(Callback cb);
+
     private native final void native_configure(
             String[] keys, Object[] values,
             Surface surface, MediaCrypto crypto, int flags);
@@ -385,7 +512,14 @@
      * @throws MediaCodec.CodecException upon codec error. Note that some codec errors
      * for start may be attributed to future method calls.
      */
-    public native final void start();
+    public final void start() {
+        native_start();
+        synchronized(mBufferLock) {
+            cacheBuffers(true /* input */);
+            cacheBuffers(false /* input */);
+        }
+    }
+    private native final void native_start();
 
     /**
      * Finish the decode/encode session, note that the codec instance
@@ -396,9 +530,11 @@
      */
     public final void stop() {
         native_stop();
+        freeAllTrackedBuffers();
 
         if (mEventHandler != null) {
-            mEventHandler.removeMessages(EVENT_NOTIFY);
+            mEventHandler.removeMessages(EVENT_CALLBACK);
+            mEventHandler.removeMessages(EVENT_SET_CALLBACK);
         }
     }
 
@@ -411,7 +547,19 @@
      * @throws IllegalStateException if not in the Executing state.
      * @throws MediaCodec.CodecException upon codec error.
      */
-    public native final void flush();
+    public final void flush() {
+        synchronized(mBufferLock) {
+            invalidateByteBuffers(mCachedInputBuffers);
+            invalidateByteBuffers(mCachedOutputBuffers);
+            invalidateByteBuffers(mDequeuedInputBuffers);
+            invalidateByteBuffers(mDequeuedOutputBuffers);
+            freeImages(mDequeuedInputImages);
+            freeImages(mDequeuedOutputImages);
+        }
+        native_flush();
+    }
+
+    private native final void native_flush();
 
     /**
      * Thrown when an internal codec error occurs.
@@ -497,8 +645,11 @@
 
     /**
      * After filling a range of the input buffer at the specified index
-     * submit it to the component.
-     *
+     * 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.
+     * <p>
      * Many decoders require the actual compressed data stream to be
      * preceded by "codec specific data", i.e. setup data used to initialize
      * the codec such as PPS/SPS in the case of AVC video or code tables
@@ -506,9 +657,16 @@
      * The class {@link android.media.MediaExtractor} provides codec
      * specific data as part of
      * the returned track format in entries named "csd-0", "csd-1" ...
-     *
-     * These buffers should be submitted using the flag {@link #BUFFER_FLAG_CODEC_CONFIG}.
-     *
+     * <p>
+     * These buffers can be submitted directly after {@link #start} or
+     * {@link #flush} by specifying the flag {@link
+     * #BUFFER_FLAG_CODEC_CONFIG}.  However, if you configure the
+     * codec with a {@link MediaFormat} containing these keys, they
+     * will be automatically submitted by MediaCodec directly after
+     * start.  Therefore, the use of {@link
+     * #BUFFER_FLAG_CODEC_CONFIG} flag is discouraged and is
+     * recommended only for advanced users.
+     * <p>
      * To indicate that this is the final piece of input data (or rather that
      * no more input data follows unless the decoder is subsequently flushed)
      * specify the flag {@link #BUFFER_FLAG_END_OF_STREAM}.
@@ -517,15 +675,31 @@
      *              in a call to {@link #dequeueInputBuffer}.
      * @param offset The byte offset into the input buffer at which the data starts.
      * @param size The number of bytes of valid input data.
-     * @param presentationTimeUs The time at which this buffer should be rendered.
-     * @param flags A bitmask of flags {@link #BUFFER_FLAG_SYNC_FRAME},
-     *              {@link #BUFFER_FLAG_CODEC_CONFIG} or {@link #BUFFER_FLAG_END_OF_STREAM}.
+     * @param presentationTimeUs The presentation timestamp in microseconds for this
+     *                           buffer. This is normally the media time at which this
+     *                           buffer should be presented (rendered).
+     * @param flags A bitmask of flags
+     *              {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+     *              While not prohibited, most codecs do not use the
+     *              {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
      * @throws IllegalStateException if not in the Executing state.
      * @throws MediaCodec.CodecException upon codec error.
      * @throws CryptoException if a crypto object has been specified in
      *         {@link #configure}
      */
-    public native final void queueInputBuffer(
+    public final void queueInputBuffer(
+            int index,
+            int offset, int size, long presentationTimeUs, int flags)
+        throws CryptoException {
+        synchronized(mBufferLock) {
+            invalidateByteBuffer(mCachedInputBuffers, index);
+            updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null);
+        }
+        native_queueInputBuffer(
+                index, offset, size, presentationTimeUs, flags);
+    }
+
+    private native final void native_queueInputBuffer(
             int index,
             int offset, int size, long presentationTimeUs, int flags)
         throws CryptoException;
@@ -618,16 +792,34 @@
      * @param offset The byte offset into the input buffer at which the data starts.
      * @param info Metadata required to facilitate decryption, the object can be
      *             reused immediately after this call returns.
-     * @param presentationTimeUs The time at which this buffer should be rendered.
-     * @param flags A bitmask of flags {@link #BUFFER_FLAG_SYNC_FRAME},
-     *              {@link #BUFFER_FLAG_CODEC_CONFIG} or {@link #BUFFER_FLAG_END_OF_STREAM}.
+     * @param presentationTimeUs The presentation timestamp in microseconds for this
+     *                           buffer. This is normally the media time at which this
+     *                           buffer should be presented (rendered).
+     * @param flags A bitmask of flags
+     *              {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+     *              While not prohibited, most codecs do not use the
+     *              {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
      * @throws IllegalStateException if not in the Executing state.
      * @throws MediaCodec.CodecException upon codec error.
      * @throws CryptoException if an error occurs while attempting to decrypt the buffer.
      *              An error code associated with the exception helps identify the
      *              reason for the failure.
      */
-    public native final void queueSecureInputBuffer(
+    public final void queueSecureInputBuffer(
+            int index,
+            int offset,
+            CryptoInfo info,
+            long presentationTimeUs,
+            int flags) throws CryptoException {
+        synchronized(mBufferLock) {
+            invalidateByteBuffer(mCachedInputBuffers, index);
+            updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null);
+        }
+        native_queueSecureInputBuffer(
+                index, offset, info, presentationTimeUs, flags);
+    }
+
+    private native final void native_queueSecureInputBuffer(
             int index,
             int offset,
             CryptoInfo info,
@@ -644,7 +836,17 @@
      * @throws IllegalStateException if not in the Executing state.
      * @throws MediaCodec.CodecException upon codec error.
      */
-    public native final int dequeueInputBuffer(long timeoutUs);
+    public final int dequeueInputBuffer(long timeoutUs) {
+        int res = native_dequeueInputBuffer(timeoutUs);
+        if (res >= 0) {
+            synchronized(mBufferLock) {
+                validateInputByteBuffer(mCachedInputBuffers, res);
+            }
+        }
+        return res;
+    }
+
+    private native final int native_dequeueInputBuffer(long timeoutUs);
 
     /**
      * If a non-negative timeout had been specified in the call
@@ -654,7 +856,10 @@
 
     /**
      * The output format has changed, subsequent data will follow the new
-     * format. {@link #getOutputFormat} returns the new format.
+     * format. {@link #getOutputFormat()} returns the new format.  Note, that
+     * you can also use the new {@link #getOutputFormat(int)} method to
+     * get the format for a specific output buffer.  This frees you from
+     * having to track output format changes.
      */
     public static final int INFO_OUTPUT_FORMAT_CHANGED  = -2;
 
@@ -662,6 +867,11 @@
      * The output buffers have changed, the client must refer to the new
      * set of output buffers returned by {@link #getOutputBuffers} from
      * this point on.
+     *
+     * @deprecated This return value can be ignored as {@link
+     * #getOutputBuffers} has been deprecated.  Client should
+     * request a current buffer using on of the get-buffer or
+     * get-image methods each time one has been dequeued.
      */
     public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3;
 
@@ -674,13 +884,31 @@
      * @throws IllegalStateException if not in the Executing state.
      * @throws MediaCodec.CodecException upon codec error.
      */
-    public native final int dequeueOutputBuffer(
+    public final int dequeueOutputBuffer(
+            BufferInfo info, long timeoutUs) {
+        int res = native_dequeueOutputBuffer(info, timeoutUs);
+        synchronized(mBufferLock) {
+            if (res == INFO_OUTPUT_BUFFERS_CHANGED) {
+                cacheBuffers(false /* input */);
+            } else if (res >= 0) {
+                validateOutputByteBuffer(mCachedOutputBuffers, res, info);
+            }
+        }
+        return res;
+    }
+
+    private native final int native_dequeueOutputBuffer(
             BufferInfo info, long timeoutUs);
 
     /**
      * If you are done with a buffer, use this call to return the buffer to
      * 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
+     *
      * @param index The index of a client-owned output buffer previously returned
      *              from a call to {@link #dequeueOutputBuffer}.
      * @param render If a valid surface was specified when configuring the codec,
@@ -689,6 +917,10 @@
      * @throws MediaCodec.CodecException upon codec error.
      */
     public final void releaseOutputBuffer(int index, boolean render) {
+        synchronized(mBufferLock) {
+            invalidateByteBuffer(mCachedOutputBuffers, index);
+            updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null);
+        }
         releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
     }
 
@@ -729,12 +961,22 @@
      * </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
+     *
      * @param index The index of a client-owned output buffer previously returned
      *              from a call to {@link #dequeueOutputBuffer}.
      * @param renderTimestampNs The timestamp to associate with this buffer when
      *              it is sent to the Surface.
+     * @throws IllegalStateException if not in the Executing state.
+     * @throws MediaCodec.CodecException upon codec error.
      */
     public final void releaseOutputBuffer(int index, long renderTimestampNs) {
+        synchronized(mBufferLock) {
+            invalidateByteBuffer(mCachedOutputBuffers, index);
+            updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null);
+        }
         releaseOutputBuffer(
                 index, true /* render */, true /* updatePTS */, renderTimestampNs);
     }
@@ -753,34 +995,341 @@
 
     /**
      * Call this after dequeueOutputBuffer signals a format change by returning
-     * {@link #INFO_OUTPUT_FORMAT_CHANGED}
-     * @throws IllegalStateException if not in the Executing state.
+     * {@link #INFO_OUTPUT_FORMAT_CHANGED}.
+     * You can also call this after {@link #configure} returns
+     * successfully to get the output format initially configured
+     * for the codec.  Do this to determine what optional
+     * configuration parameters were supported by the codec.
+     *
+     * @throws IllegalStateException if not in the Executing or
+     *                               Configured state.
      * @throws MediaCodec.CodecException upon codec error.
      */
     public final MediaFormat getOutputFormat() {
-        return new MediaFormat(getOutputFormatNative());
+        return new MediaFormat(getFormatNative(false /* input */));
     }
 
-    private native final Map<String, Object> getOutputFormatNative();
+    /**
+     * Call this after {@link #configure} returns successfully to
+     * get the input format accepted by the codec. Do this to
+     * determine what optional configuration parameters were
+     * supported by the codec.
+     *
+     * @throws IllegalStateException if not in the Executing or
+     *                               Configured state.
+     * @throws MediaCodec.CodecException upon codec error.
+     */
+    public final MediaFormat getInputFormat() {
+        return new MediaFormat(getFormatNative(true /* input */));
+    }
 
     /**
-     * Call this after start() returns.
+     * Returns the output format for a specific output buffer.
+     *
+     * @param index The index of a client-owned input buffer previously
+     *              returned from a call to {@link #dequeueInputBuffer}.
+     *
+     * @return the format for the output buffer, or null if the index
+     * is not a dequeued output buffer.
+     */
+    public final MediaFormat getOutputFormat(int index) {
+        return new MediaFormat(getOutputFormatNative(index));
+    }
+
+    private native final Map<String, Object> getFormatNative(boolean input);
+
+    private native final Map<String, Object> getOutputFormatNative(int index);
+
+    private ByteBuffer[] mCachedInputBuffers;
+    private ByteBuffer[] mCachedOutputBuffers;
+    private ByteBuffer[] mDequeuedInputBuffers;
+    private ByteBuffer[] mDequeuedOutputBuffers;
+    private Image[] mDequeuedInputImages;
+    private Image[] mDequeuedOutputImages;
+    final private Object mBufferLock;
+
+    private final void invalidateByteBuffer(
+            ByteBuffer[] buffers, int index) {
+        if (index >= 0 && index < buffers.length) {
+            ByteBuffer buffer = buffers[index];
+            if (buffer != null) {
+                buffer.setAccessible(false);
+            }
+        }
+    }
+
+    private final void validateInputByteBuffer(
+            ByteBuffer[] buffers, int index) {
+        if (index >= 0 && index < buffers.length) {
+            ByteBuffer buffer = buffers[index];
+            if (buffer != null) {
+                buffer.setAccessible(true);
+                buffer.clear();
+            }
+        }
+    }
+
+    private final void validateOutputByteBuffer(
+            ByteBuffer[] buffers, int index, BufferInfo info) {
+        if (index >= 0 && index < buffers.length) {
+            ByteBuffer buffer = buffers[index];
+            if (buffer != null) {
+                buffer.setAccessible(true);
+                buffer.limit(info.offset + info.size).position(info.offset);
+            }
+        }
+    }
+
+    private final void invalidateByteBuffers(ByteBuffer[] buffers) {
+        if (buffers != null) {
+            for (ByteBuffer buffer: buffers) {
+                if (buffer != null) {
+                    buffer.setAccessible(false);
+                }
+            }
+        }
+    }
+
+    private final void freeByteBuffer(ByteBuffer buffer) {
+        if (buffer != null /* && buffer.isDirect() */) {
+            // all of our ByteBuffers are direct
+            java.nio.NioUtils.freeDirectBuffer(buffer);
+        }
+    }
+
+    private final void freeByteBuffers(ByteBuffer[] buffers) {
+        if (buffers != null) {
+            for (ByteBuffer buffer: buffers) {
+                freeByteBuffer(buffer);
+            }
+        }
+    }
+
+    private final void freeImage(Image image) {
+        if (image != null) {
+            image.close();
+        }
+    }
+
+    private final void freeImages(Image[] images) {
+        if (images != null) {
+            for (Image image: images) {
+                freeImage(image);
+            }
+        }
+    }
+
+    private final void freeAllTrackedBuffers() {
+        freeByteBuffers(mCachedInputBuffers);
+        freeByteBuffers(mCachedOutputBuffers);
+        freeImages(mDequeuedInputImages);
+        freeImages(mDequeuedOutputImages);
+        freeByteBuffers(mDequeuedInputBuffers);
+        freeByteBuffers(mDequeuedOutputBuffers);
+        mCachedInputBuffers = null;
+        mCachedOutputBuffers = null;
+        mDequeuedInputImages = null;
+        mDequeuedOutputImages = null;
+        mDequeuedInputBuffers = null;
+        mDequeuedOutputBuffers = null;
+    }
+
+    private final void cacheBuffers(boolean input) {
+        ByteBuffer[] buffers = getBuffers(input);
+        invalidateByteBuffers(buffers);
+        if (input) {
+            mCachedInputBuffers = buffers;
+            mDequeuedInputImages = new Image[buffers.length];
+            mDequeuedInputBuffers = new ByteBuffer[buffers.length];
+        } else {
+            mCachedOutputBuffers = buffers;
+            mDequeuedOutputImages = new Image[buffers.length];
+            mDequeuedOutputBuffers = new ByteBuffer[buffers.length];
+        }
+    }
+
+    /**
+     * Retrieve the set of input buffers.  Call this after start()
+     * returns. After calling this method, any ByteBuffers
+     * previously returned by an earlier call to this method MUST no
+     * longer be used.
+     *
+     * @deprecated Use the new {@link #getInputBuffer} method instead
+     * each time an input buffer is dequeued.
+     *
      * @throws IllegalStateException if not in the Executing state.
      * @throws MediaCodec.CodecException upon codec error.
      */
     public ByteBuffer[] getInputBuffers() {
-        return getBuffers(true /* input */);
+        if (mCachedInputBuffers == null) {
+            throw new IllegalStateException();
+        }
+        // FIXME: check codec status
+        return mCachedInputBuffers;
     }
 
     /**
-     * Call this after start() returns and whenever dequeueOutputBuffer
-     * signals an output buffer change by returning
-     * {@link #INFO_OUTPUT_BUFFERS_CHANGED}
-     * @throws IllegalStateException if not in the Executing state.
+     * Retrieve the set of output buffers.  Call this after start()
+     * returns and whenever dequeueOutputBuffer signals an output
+     * buffer change by returning {@link
+     * #INFO_OUTPUT_BUFFERS_CHANGED}. After calling this method, any
+     * ByteBuffers previously returned by an earlier call to this
+     * method MUST no longer be used.
+     *
+     * @deprecated Use the new {@link #getOutputBuffer} method instead
+     * each time an output buffer is dequeued.  This method is not
+     * supported if codec is configured in asynchronous mode.
+     *
+     * @throws IllegalStateException if not in the Executing state,
+     *         or codec is configured in asynchronous mode.
      * @throws MediaCodec.CodecException upon codec error.
      */
     public ByteBuffer[] getOutputBuffers() {
-        return getBuffers(false /* input */);
+        if (mCachedOutputBuffers == null) {
+            throw new IllegalStateException();
+        }
+        // FIXME: check codec status
+        return mCachedOutputBuffers;
+    }
+
+    private boolean updateDequeuedByteBuffer(
+            ByteBuffer[] buffers, int index, ByteBuffer newBuffer) {
+        if (index < 0 || index >= buffers.length) {
+            return false;
+        }
+        freeByteBuffer(buffers[index]);
+        buffers[index] = newBuffer;
+        return newBuffer != null;
+    }
+
+    private boolean updateDequeuedImage(
+            Image[] images, int index, Image newImage) {
+        if (index < 0 || index >= images.length) {
+            return false;
+        }
+        freeImage(images[index]);
+        images[index] = newImage;
+        return newImage != null;
+    }
+
+    /**
+     * Returns a 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
+     * be used.
+     *
+     * @param index The index of a client-owned input buffer previously
+     *              returned from a call to {@link #dequeueInputBuffer},
+     *              or received via an onInputBufferAvailable callback.
+     *
+     * @return the input buffer, or null if the index is not a dequeued
+     * input buffer, or if the codec is configured for surface input.
+     *
+     * @throws IllegalStateException if not in the Executing state.
+     * @throws MediaCodec.CodecException upon codec error.
+     */
+    public ByteBuffer getInputBuffer(int index) {
+        ByteBuffer newBuffer = getBuffer(true /* input */, index);
+        synchronized(mBufferLock) {
+            if (updateDequeuedByteBuffer(mDequeuedInputBuffers, index, newBuffer)) {
+                updateDequeuedImage(mDequeuedInputImages, index, null);
+                invalidateByteBuffer(mCachedInputBuffers, index);
+                return newBuffer;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a writable Image object for a dequeued input buffer
+     * index to contain the raw input video frame.
+     *
+     * After calling this method any ByteBuffer or Image object
+     * previously returned for the same input index MUST no longer
+     * be used.
+     *
+     * @param index The index of a client-owned input buffer previously
+     *              returned from a call to {@link #dequeueInputBuffer},
+     *              or received via an onInputBufferAvailable callback.
+     *
+     * @return the input image, or null if the index is not a
+     * dequeued input buffer, or not a ByteBuffer that contains a
+     * raw image.
+     *
+     * @throws IllegalStateException if not in the Executing state.
+     * @throws MediaCodec.CodecException upon codec error.
+     */
+    public Image getInputImage(int index) {
+        Image newImage = getImage(true /* input */, index);
+        if (updateDequeuedImage(mDequeuedInputImages, index, newImage)) {
+            updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null);
+            invalidateByteBuffer(mCachedInputBuffers, index);
+            return newImage;
+        }
+        return null;
+    }
+
+    /**
+     * Returns a read-only ByteBuffer for a dequeued output buffer
+     * index. The position and limit of the returned buffer are set
+     * to the valid output data.
+     *
+     * After calling this method, any ByteBuffer or Image object
+     * previously returned for the same output index MUST no longer
+     * be used.
+     *
+     * @param index The index of a client-owned output buffer previously
+     *              returned from a call to {@link #dequeueOutputBuffer},
+     *              or received via an onOutputBufferAvailable callback.
+     *
+     * @return the output buffer, or null if the index is not a dequeued
+     * output buffer, or the codec is configured with an output surface.
+     *
+     * @throws IllegalStateException if not in the Executing state.
+     * @throws MediaCodec.CodecException upon codec error.
+     */
+    public ByteBuffer getOutputBuffer(int index) {
+        ByteBuffer newBuffer = getBuffer(false /* input */, index);
+        synchronized(mBufferLock) {
+            if (updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, newBuffer)) {
+                updateDequeuedImage(mDequeuedOutputImages, index, null);
+                invalidateByteBuffer(mCachedOutputBuffers, index);
+                return newBuffer;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a read-only Image object for a dequeued output buffer
+     * index that contains the raw video frame.
+     *
+     * After calling this method, any ByteBuffer or Image object previously
+     * returned for the same output index MUST no longer be used.
+     *
+     * @param index The index of a client-owned output buffer previously
+     *              returned from a call to {@link #dequeueOutputBuffer},
+     *              or received via an onOutputBufferAvailable callback.
+     *
+     * @return the output image, or null if the index is not a
+     * dequeued output buffer, not a raw video frame, or if the codec
+     * was configured with an output surface.
+     *
+     * @throws IllegalStateException if not in the Executing state.
+     * @throws MediaCodec.CodecException upon codec error.
+     */
+    public Image getOutputImage(int index) {
+        Image newImage = getImage(false /* input */, index);
+        synchronized(mBufferLock) {
+            if (updateDequeuedImage(mDequeuedOutputImages, index, newImage)) {
+                updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null);
+                invalidateByteBuffer(mCachedOutputBuffers, index);
+                return newImage;
+            }
+        }
+        return null;
     }
 
     /**
@@ -855,44 +1404,71 @@
     }
 
     /**
-     * Sets the codec listener for actionable MediaCodec events.
-     * <p>Call this method with a null listener to stop receiving event notifications.
+     * Sets an asynchronous callback for actionable MediaCodec events.
      *
-     * @param cb The listener that will run.
+     * If the client intends to use the component in asynchronous mode,
+     * a valid callback should be provided before {@link #configure} is called.
      *
-     * @hide
+     * When asynchronous callback is enabled, the client should not call
+     * {@link #dequeueInputBuffer(long)} or {@link #dequeueOutputBuffer(BufferInfo, long)}
+     *
+     * @param cb The callback that will run.
      */
-    public void setNotificationCallback(NotificationCallback cb) {
-        mNotificationCallback = cb;
+    public void setCallback(/* MediaCodec. */ Callback cb) {
+        if (mEventHandler != null) {
+            // set java callback on handler
+            Message msg = mEventHandler.obtainMessage(EVENT_SET_CALLBACK, 0, 0, cb);
+            mEventHandler.sendMessage(msg);
+
+            // set native handler here, don't post to handler because
+            // it may cause the callback to be delayed and set in a wrong state,
+            // and MediaCodec is already doing it on looper.
+            native_setCallback(cb);
+        }
     }
 
     /**
-     * MediaCodec listener interface.  Used to notify the user of MediaCodec
-     * when there are available input and/or output buffers, a change in
-     * configuration or when a codec error happened.
-     *
-     * @hide
+     * MediaCodec callback interface. Used to notify the user asynchronously
+     * of various MediaCodec events.
      */
-    public static abstract class NotificationCallback {
+    public static abstract class Callback {
         /**
-         * Called on the listener to notify that there is an actionable
-         * MediaCodec event.  The application should call {@link #dequeueOutputBuffer}
-         * to receive the configuration change event, codec error or an
-         * available output buffer.  It should also call  {@link #dequeueInputBuffer}
-         * to receive any available input buffer.  For best performance, it
-         * is recommended to exhaust both available input and output buffers in
-         * the handling of a single callback, by calling the dequeue methods
-         * repeatedly with a zero timeout until {@link #INFO_TRY_AGAIN_LATER} is returned.
+         * Called when an input buffer becomes available.
          *
-         * @param codec the MediaCodec instance that has an actionable event.
-         *
+         * @param codec The MediaCodec object.
+         * @param index The index of the available input buffer.
          */
-        public abstract void onCodecNotify(MediaCodec codec);
+        public abstract void onInputBufferAvailable(MediaCodec codec, int index);
+
+        /**
+         * Called when an output buffer becomes available.
+         *
+         * @param codec The MediaCodec object.
+         * @param index The index of the available output buffer.
+         * @param info Info regarding the available output buffer {@link MediaCodec.BufferInfo}.
+         */
+        public abstract void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info);
+
+        /**
+         * Called when the MediaCodec encountered an error
+         *
+         * @param codec The MediaCodec object.
+         * @param e The {@link MediaCodec.CodecException} object describing the error.
+         */
+        public abstract void onError(MediaCodec codec, CodecException e);
+
+        /**
+         * Called when the output format has changed
+         *
+         * @param codec The MediaCodec object.
+         * @param format The new output format.
+         */
+        public abstract void onOutputFormatChanged(MediaCodec codec, MediaFormat format);
     }
 
     private void postEventFromNative(
             int what, int arg1, int arg2, Object obj) {
-        if (mEventHandler != null && mNotificationCallback != null) {
+        if (mEventHandler != null) {
             Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj);
             mEventHandler.sendMessage(msg);
         }
@@ -913,6 +1489,10 @@
 
     private native final ByteBuffer[] getBuffers(boolean input);
 
+    private native final ByteBuffer getBuffer(boolean input, int index);
+
+    private native final Image getImage(boolean input, int index);
+
     private static native final void native_init();
 
     private native final void native_setup(
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 490a8fd..ab65ba0 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -631,6 +631,7 @@
     private static final int INVOKE_ID_SELECT_TRACK = 4;
     private static final int INVOKE_ID_DESELECT_TRACK = 5;
     private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6;
+    private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
 
     /**
      * Create a request parcel which can be routed to the native media
@@ -1664,7 +1665,6 @@
         public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
         public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
         public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
-        /** @hide */
         public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
 
         final int mTrackType;
@@ -2085,6 +2085,7 @@
             throws IllegalArgumentException, IllegalStateException {
         if (!availableMimeTypeForExternalSource(mimeType)) {
             throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mimeType);
+
         }
 
         Parcel request = Parcel.obtain();
@@ -2104,6 +2105,49 @@
     }
 
     /**
+     * Returns the index of the audio, video, or subtitle track currently selected for playback,
+     * The return value is an index into the array returned by {@link #getTrackInfo()}, and can
+     * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}.
+     *
+     * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
+     * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
+     * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
+     * @return index of the audio, video, or subtitle track currently selected for playback;
+     * a negative integer is returned when there is no selected track for {@code trackType} or
+     * when {@code trackType} is not one of audio, video, or subtitle.
+     * @throws IllegalStateException if called after {@link #release()}
+     *
+     * @see {@link #getTrackInfo()}
+     * @see {@link #selectTrack(int)}
+     * @see {@link #deselectTrack(int)}
+     */
+    public int getSelectedTrack(int trackType) throws IllegalStateException {
+        if (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE && mSubtitleController != null) {
+            SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack();
+            if (subtitleTrack != null) {
+                int index = mOutOfBandSubtitleTracks.indexOf(subtitleTrack);
+                if (index >= 0) {
+                    return mInbandSubtitleTracks.length + index;
+                }
+            }
+        }
+
+        Parcel request = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        try {
+            request.writeInterfaceToken(IMEDIA_PLAYER);
+            request.writeInt(INVOKE_ID_GET_SELECTED_TRACK);
+            request.writeInt(trackType);
+            invoke(request, reply);
+            int selectedTrack = reply.readInt();
+            return selectedTrack;
+        } finally {
+            request.recycle();
+            reply.recycle();
+        }
+    }
+
+    /**
      * Selects a track.
      * <p>
      * If a MediaPlayer is in invalid state, it throws an IllegalStateException exception.
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 21e2f4b..f8c0494 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -25,8 +25,8 @@
 import android.view.Surface;
 
 import java.io.FileDescriptor;
-import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.lang.ref.WeakReference;
 
 /**
@@ -713,11 +713,11 @@
     public void prepare() throws IllegalStateException, IOException
     {
         if (mPath != null) {
-            FileOutputStream fos = new FileOutputStream(mPath);
+            RandomAccessFile file = new RandomAccessFile(mPath, "rws");
             try {
-                _setOutputFile(fos.getFD(), 0, 0);
+                _setOutputFile(file.getFD(), 0, 0);
             } finally {
-                fos.close();
+                file.close();
             }
         } else if (mFd != null) {
             _setOutputFile(mFd, 0, 0);
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
new file mode 100644
index 0000000..f7967f1
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiopolicy;
+
+import android.annotation.IntDef;
+import android.media.AudioFormat;
+import android.media.AudioSystem;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide CANDIDATE FOR PUBLIC API
+ */
+public class AudioMix {
+
+    private AudioMixingRule mRule;
+    private AudioFormat mFormat;
+    private int mRouteFlags;
+
+    /**
+     * All parameters are guaranteed valid through the Builder.
+     */
+    private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags) {
+        mRule = rule;
+        mFormat = format;
+        mRouteFlags = routeFlags;
+    }
+
+    /**
+     * An audio mix behavior where the output of the mix is sent to the original destination of
+     * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
+     */
+    public static final int ROUTE_FLAG_RENDER    = 0x1;
+    /**
+     * An audio mix behavior where the output of the mix is rerouted back to the framework and
+     * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord}
+     * APIs.
+     */
+    public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
+
+    int getRouteFlags() {
+        return mRouteFlags;
+    }
+
+    AudioFormat getFormat() {
+        return mFormat;
+    }
+
+    AudioMixingRule getRule() {
+        return mRule;
+    }
+
+    /** @hide */
+    @IntDef(flag = true,
+            value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RouteFlags {}
+
+    /**
+     * Builder class for {@link AudioMix} objects
+     *
+     */
+    public static class Builder {
+        private AudioMixingRule mRule = null;
+        private AudioFormat mFormat = null;
+        private int mRouteFlags = 0;
+
+        /**
+         * @hide
+         * Only used by AudioPolicyConfig, not a public API.
+         */
+        Builder() { }
+
+        /**
+         * Construct an instance for the given {@link AudioMixingRule}.
+         * @param rule a non-null {@link AudioMixingRule} instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder(AudioMixingRule rule)
+                throws IllegalArgumentException {
+            if (rule == null) {
+                throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
+            }
+            mRule = rule;
+        }
+
+        /**
+         * @hide
+         * Only used by AudioPolicyConfig, not a public API.
+         * @param rule
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder setMixingRule(AudioMixingRule rule)
+                throws IllegalArgumentException {
+            if (rule == null) {
+                throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
+            }
+            mRule = rule;
+            return this;
+        }
+
+        /**
+         * Sets the {@link AudioFormat} for the mix.
+         * @param format a non-null {@link AudioFormat} instance.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder setFormat(AudioFormat format)
+                throws IllegalArgumentException {
+            if (format == null) {
+                throw new IllegalArgumentException("Illegal null AudioFormat argument");
+            }
+            mFormat = format;
+            return this;
+        }
+
+        /**
+         * Sets the routing behavior for the mix.
+         * @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK},
+         *     {@link AudioMix#ROUTE_FLAG_RENDER}
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder setRouteFlags(@RouteFlags int routeFlags)
+                throws IllegalArgumentException {
+            if (routeFlags == 0) {
+                throw new IllegalArgumentException("Illegal empty route flags");
+            }
+            if ((routeFlags & (ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER)) == 0) {
+                throw new IllegalArgumentException("Invalid route flags 0x"
+                        + Integer.toHexString(routeFlags) + "when creating an AudioMix");
+            }
+            mRouteFlags = routeFlags;
+            return this;
+        }
+
+        /**
+         * Combines all of the settings and return a new {@link AudioMix} object.
+         * @return a new {@link AudioMix} object
+         * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
+         */
+        public AudioMix build() throws IllegalArgumentException {
+            if (mRule == null) {
+                throw new IllegalArgumentException("Illegal null AudioMixingRule");
+            }
+            if (mRouteFlags == 0) {
+                // no route flags set, use default
+                mRouteFlags = ROUTE_FLAG_RENDER;
+            }
+            if (mFormat == null) {
+                int rate = AudioSystem.getPrimaryOutputSamplingRate();
+                if (rate <= 0) {
+                    rate = 44100;
+                }
+                mFormat = new AudioFormat.Builder().setSampleRate(rate).build();
+            }
+            return new AudioMix(mRule, mFormat, mRouteFlags);
+        }
+    }
+}
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
new file mode 100644
index 0000000..ced7881
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.audiopolicy;
+
+import android.media.AudioAttributes;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+
+/**
+ * @hide CANDIDATE FOR PUBLIC API
+ *
+ * Here's an example of creating a mixing rule for all media playback:
+ * <pre>
+ * AudioAttributes mediaAttr = new AudioAttributes.Builder()
+ *         .setUsage(AudioAttributes.USAGE_MEDIA)
+ *         .build();
+ * AudioMixingRule mediaRule = new AudioMixingRule.Builder()
+ *         .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
+ *         .build();
+ * </pre>
+ */
+public class AudioMixingRule {
+
+    private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) {
+        mCriteria = criteria;
+    }
+
+    /**
+     * A rule requiring the usage information of the {@link AudioAttributes} to match
+     */
+    public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
+    /**
+     * A rule requiring the usage information of the {@link AudioAttributes} to differ
+     */
+    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1;
+
+    static final class AttributeMatchCriterion {
+        AudioAttributes mAttr;
+        int mRule;
+
+        AttributeMatchCriterion(AudioAttributes attributes, int rule) {
+            mAttr = attributes;
+            mRule = rule;
+        }
+    }
+
+    private ArrayList<AttributeMatchCriterion> mCriteria;
+    ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; }
+
+    /**
+     * Builder class for {@link AudioMixingRule} objects
+     *
+     */
+    public static class Builder {
+        private ArrayList<AttributeMatchCriterion> mCriteria;
+
+        /**
+         * Constructs a new Builder with no rules.
+         */
+        public Builder() {
+            mCriteria = new ArrayList<AttributeMatchCriterion>();
+        }
+
+        /**
+         * Add a rule for the selection of which streams are mixed together.
+         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+         *     rule hasn't been set yet.
+         * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder addRule(AudioAttributes attrToMatch, int rule)
+                throws IllegalArgumentException {
+            if (attrToMatch == null) {
+                throw new IllegalArgumentException("Illegal null AudioAttributes argument");
+            }
+            if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+                throw new IllegalArgumentException("Illegal rule value " + rule);
+            }
+            synchronized (mCriteria) {
+                Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
+                while (crIterator.hasNext()) {
+                    final AttributeMatchCriterion criterion = crIterator.next();
+                    if ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
+                            || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+                        // "usage"-based rule
+                        if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
+                            if (criterion.mRule == rule) {
+                                // rule already exists, we're done
+                                return this;
+                            } else {
+                                // criterion already exists with a another rule, it is incompatible
+                                throw new IllegalArgumentException("Contradictory rule exists for "
+                                        + attrToMatch);
+                            }
+                        }
+                    }
+                }
+                // rule didn't exist, add it
+                mCriteria.add(new AttributeMatchCriterion(attrToMatch, rule));
+            }
+            return this;
+        }
+
+        /**
+         * Combines all of the matching and exclusion rules that have been set and return a new
+         * {@link AudioMixingRule} object.
+         * @return a new {@link AudioMixingRule} object
+         */
+        public AudioMixingRule build() {
+            return new AudioMixingRule(mCriteria);
+        }
+    }
+}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
new file mode 100644
index 0000000..314eb88
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.audiopolicy;
+
+import android.annotation.IntDef;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * @hide CANDIDATE FOR PUBLIC API
+ * AudioPolicy provides access to the management of audio routing and audio focus.
+ */
+public class AudioPolicy {
+
+    private static final String TAG = "AudioPolicy";
+
+    /**
+     * The status of an audio policy that cannot be used because it is invalid.
+     */
+    public static final int POLICY_STATUS_INVALID = 0;
+    /**
+     * The status of an audio policy that is valid but cannot be used because it is not registered.
+     */
+    public static final int POLICY_STATUS_UNREGISTERED = 1;
+    /**
+     * The status of an audio policy that is valid, successfully registered and thus active.
+     */
+    public static final int POLICY_STATUS_REGISTERED = 2;
+
+    private int mStatus;
+    private AudioPolicyStatusListener mStatusListener = null;
+
+    private final IBinder mToken = new Binder();
+    /** @hide */
+    public IBinder token() { return mToken; }
+
+    private AudioPolicyConfig mConfig;
+    /** @hide */
+    public AudioPolicyConfig getConfig() { return mConfig; }
+
+    /**
+     * The parameter is guaranteed non-null through the Builder
+     */
+    private AudioPolicy(AudioPolicyConfig config) {
+        mConfig = config;
+        if (mConfig.mMixes.isEmpty()) {
+            mStatus = POLICY_STATUS_INVALID;
+        } else {
+            mStatus = POLICY_STATUS_UNREGISTERED;
+        }
+    }
+
+    /**
+     * Builder class for {@link AudioPolicy} objects
+     */
+    public static class Builder {
+        private ArrayList<AudioMix> mMixes;
+
+        /**
+         * Constructs a new Builder with no audio mixes.
+         */
+        public Builder() {
+            mMixes = new ArrayList<AudioMix>();
+        }
+
+        /**
+         * Add an {@link AudioMix} to be part of the audio policy being built.
+         * @param mix a non-null {@link AudioMix} to be part of the audio policy.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder addMix(AudioMix mix) throws IllegalArgumentException {
+            if (mix == null) {
+                throw new IllegalArgumentException("Illegal null AudioMix argument");
+            }
+            mMixes.add(mix);
+            return this;
+        }
+
+        public AudioPolicy build() {
+            return new AudioPolicy(new AudioPolicyConfig(mMixes));
+        }
+    }
+
+
+    public int getStatus() {
+        return mStatus;
+    }
+
+    public static abstract class AudioPolicyStatusListener {
+        void onStatusChange() {}
+        void onMixStateUpdate(AudioMix mix) {}
+    }
+
+    void setStatusListener(AudioPolicyStatusListener l) {
+        mStatusListener = l;
+    }
+
+    /** @hide */
+    @Override
+    public String toString () {
+        String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
+        textDump += "config=" + mConfig.toString();
+        return (textDump);
+    }
+
+    /** @hide */
+    @IntDef({
+        POLICY_STATUS_INVALID,
+        POLICY_STATUS_REGISTERED,
+        POLICY_STATUS_UNREGISTERED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PolicyStatus {}
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/media/java/android/media/audiopolicy/AudioPolicyConfig.aidl
similarity index 89%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to media/java/android/media/audiopolicy/AudioPolicyConfig.aidl
index 3da1321..3ae1175 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.bluetooth.le;
+package android.media.audiopolicy;
 
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable AudioPolicyConfig;
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
new file mode 100644
index 0000000..2fc6d58
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.audiopolicy;
+
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioMixingRule.AttributeMatchCriterion;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ * Internal storage class for AudioPolicy configuration.
+ */
+public class AudioPolicyConfig implements Parcelable {
+
+    private static final String TAG = "AudioPolicyConfig";
+
+    ArrayList<AudioMix> mMixes;
+
+    AudioPolicyConfig(ArrayList<AudioMix> mixes) {
+        mMixes = mixes;
+    }
+
+    /**
+     * Add an {@link AudioMix} to be part of the audio policy being built.
+     * @param mix a non-null {@link AudioMix} to be part of the audio policy.
+     * @return the same Builder instance.
+     * @throws IllegalArgumentException
+     */
+    public void addMix(AudioMix mix) throws IllegalArgumentException {
+        if (mix == null) {
+            throw new IllegalArgumentException("Illegal null AudioMix argument");
+        }
+        mMixes.add(mix);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMixes.size());
+        for (AudioMix mix : mMixes) {
+            // write mix route flags
+            dest.writeInt(mix.getRouteFlags());
+            // write mix format
+            dest.writeInt(mix.getFormat().getSampleRate());
+            dest.writeInt(mix.getFormat().getEncoding());
+            dest.writeInt(mix.getFormat().getChannelMask());
+            // write mix rules
+            final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
+            dest.writeInt(criteria.size());
+            for (AttributeMatchCriterion criterion : criteria) {
+                dest.writeInt(criterion.mRule);
+                dest.writeInt(criterion.mAttr.getUsage());
+            }
+        }
+    }
+
+    private AudioPolicyConfig(Parcel in) {
+        mMixes = new ArrayList<AudioMix>();
+        int nbMixes = in.readInt();
+        for (int i = 0 ; i < nbMixes ; i++) {
+            final AudioMix.Builder mixBuilder = new AudioMix.Builder();
+            // read mix route flags
+            int routeFlags = in.readInt();
+            mixBuilder.setRouteFlags(routeFlags);
+            // read mix format
+            int sampleRate = in.readInt();
+            int encoding = in.readInt();
+            int channelMask = in.readInt();
+            final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
+                    .setChannelMask(channelMask).setEncoding(encoding).build();
+            mixBuilder.setFormat(format);
+            // read mix rules
+            int nbRules = in.readInt();
+            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
+            for (int j = 0 ; j < nbRules ; j++) {
+                // read the matching rules
+                int matchRule = in.readInt();
+                if ((matchRule == AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE)
+                    || (matchRule == AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)) {
+                    int usage = in.readInt();
+                    final AudioAttributes attr = new AudioAttributes.Builder()
+                            .setUsage(usage).build();
+                    ruleBuilder.addRule(attr, matchRule);
+                } else {
+                    Log.w(TAG, "Encountered unsupported rule, skipping");
+                    in.readInt();
+                }
+            }
+            mixBuilder.setMixingRule(ruleBuilder.build());
+            mMixes.add(mixBuilder.build());
+        }
+    }
+
+    /** @hide */
+    public static final Parcelable.Creator<AudioPolicyConfig> CREATOR
+            = new Parcelable.Creator<AudioPolicyConfig>() {
+        /**
+         * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
+         * @param p Parcel object to read the AudioPolicyConfig from
+         * @return a new AudioPolicyConfig created from the data in the parcel
+         */
+        public AudioPolicyConfig createFromParcel(Parcel p) {
+            return new AudioPolicyConfig(p);
+        }
+        public AudioPolicyConfig[] newArray(int size) {
+            return new AudioPolicyConfig[size];
+        }
+    };
+
+    /** @hide */
+    @Override
+    public String toString () {
+        String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
+        textDump += mMixes.size() + " AudioMix:\n";
+        for(AudioMix mix : mMixes) {
+            // write mix route flags
+            textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
+            // write mix format
+            textDump += "  rate=" + mix.getFormat().getSampleRate() + "Hz\n";
+            textDump += "  encoding=" + mix.getFormat().getEncoding() + "\n";
+            textDump += "  channels=0x";
+            textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() +"\n";
+            // write mix rules
+            final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
+            for (AttributeMatchCriterion criterion : criteria) {
+                switch(criterion.mRule) {
+                    case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE:
+                        textDump += "  exclude usage ";
+                        textDump += criterion.mAttr.usageToString();
+                        break;
+                    case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE:
+                        textDump += "  match usage ";
+                        textDump += criterion.mAttr.usageToString();
+                        break;
+                    default:
+                        textDump += "invalid rule!";
+                }
+                textDump += "\n";
+            }
+        }
+        return textDump;
+    }
+}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index bdebd7c..9a6a648 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -45,6 +45,7 @@
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void setVolume(in IBinder sessionToken, float volume, int userId);
     void tune(in IBinder sessionToken, in Uri channelUri, int userId);
+    void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
     void selectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId);
     void unselectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId);
 
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index f8590f1..875fa34 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -33,6 +33,7 @@
     // is to introduce some new concepts that will solve a number of problems in audio policy today.
     void setVolume(float volume);
     void tune(in Uri channelUri);
+    void setCaptionEnabled(boolean enabled);
     void selectTrack(in TvTrackInfo track);
     void unselectTrack(in TvTrackInfo track);
 
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 06ee4b5..0c08590 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -44,11 +44,12 @@
     private static final int DO_SET_SURFACE = 2;
     private static final int DO_SET_VOLUME = 3;
     private static final int DO_TUNE = 4;
-    private static final int DO_SELECT_TRACK = 5;
-    private static final int DO_UNSELECT_TRACK = 6;
-    private static final int DO_CREATE_OVERLAY_VIEW = 7;
-    private static final int DO_RELAYOUT_OVERLAY_VIEW = 8;
-    private static final int DO_REMOVE_OVERLAY_VIEW = 9;
+    private static final int DO_SET_CAPTION_ENABLED = 5;
+    private static final int DO_SELECT_TRACK = 6;
+    private static final int DO_UNSELECT_TRACK = 7;
+    private static final int DO_CREATE_OVERLAY_VIEW = 8;
+    private static final int DO_RELAYOUT_OVERLAY_VIEW = 9;
+    private static final int DO_REMOVE_OVERLAY_VIEW = 10;
 
     private final HandlerCaller mCaller;
 
@@ -98,6 +99,10 @@
                 mTvInputSessionImpl.tune((Uri) msg.obj);
                 return;
             }
+            case DO_SET_CAPTION_ENABLED: {
+                mTvInputSessionImpl.setCaptionEnabled((Boolean) msg.obj);
+                return;
+            }
             case DO_SELECT_TRACK: {
                 mTvInputSessionImpl.selectTrack((TvTrackInfo) msg.obj);
                 return;
@@ -148,6 +153,11 @@
     }
 
     @Override
+    public void setCaptionEnabled(boolean enabled) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_CAPTION_ENABLED, enabled));
+    }
+
+    @Override
     public void selectTrack(TvTrackInfo track) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SELECT_TRACK, track));
     }
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 521c809..f1a63ad 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -35,7 +35,6 @@
 import android.view.View;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -575,6 +574,23 @@
         }
 
         /**
+         * Enables or disables the caption for this session.
+         *
+         * @param enabled {@code true} to enable, {@code false} to disable.
+         */
+        public void setCaptionEnabled(boolean enabled) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.setCaptionEnabled(mToken, enabled, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
          * Select a track.
          *
          * @param track the track to be selected.
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4614bda..ba12e2e 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.SuppressLint;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
@@ -40,6 +41,7 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.accessibility.CaptioningManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
@@ -295,6 +297,17 @@
         public abstract boolean onTune(Uri channelUri);
 
         /**
+         * Enables or disables the caption.
+         * <p>
+         * The locale for the user's preferred captioning language can be obtained by calling
+         * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
+         *
+         * @param enabled {@code true} to enable, {@code false} to disable.
+         * @see CaptioningManager
+         */
+        public abstract void onSetCaptionEnabled(boolean enabled);
+
+        /**
          * Selects a given track.
          * <p>
          * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the
@@ -302,9 +315,10 @@
          * Also, if the select operation was successful, the implementation should call
          * {@link #dispatchTrackInfoChanged(List)} to report the updated track information.
          * </p>
+         *
          * @param track The track to be selected.
          * @return {@code true} if the select operation was successful, {@code false} otherwise.
-         * @see #dispatchTrackInfoChanged(TvTrackInfo[])
+         * @see #dispatchTrackInfoChanged
          * @see TvTrackInfo#KEY_IS_SELECTED
          */
         public boolean onSelectTrack(TvTrackInfo track) {
@@ -317,9 +331,10 @@
          * If the unselect operation was successful, the implementation should call
          * {@link #dispatchTrackInfoChanged(List)} to report the updated track information.
          * </p>
+         *
          * @param track The track to be unselected.
          * @return {@code true} if the unselect operation was successful, {@code false} otherwise.
-         * @see #dispatchTrackInfoChanged(TvTrackInfo[])
+         * @see #dispatchTrackInfoChanged
          * @see TvTrackInfo#KEY_IS_SELECTED
          */
         public boolean onUnselectTrack(TvTrackInfo track) {
@@ -492,6 +507,13 @@
         }
 
         /**
+         * Calls {@link #onSetCaptionEnabled}.
+         */
+        void setCaptionEnabled(boolean enabled) {
+            onSetCaptionEnabled(enabled);
+        }
+
+        /**
          * Calls {@link #onSelectTrack}.
          */
         void selectTrack(TvTrackInfo track) {
@@ -656,6 +678,7 @@
         return false;
     }
 
+    @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;
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 10f0c6b..7eb27b6 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.media.MediaPlayer.TrackInfo;
 import android.media.tv.TvInputManager.Session;
 import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
 import android.media.tv.TvInputManager.SessionCallback;
@@ -216,11 +215,26 @@
     }
 
     /**
+     * Enables or disables the caption in this TvView.
+     * <p>
+     * Note that this method does not take any effect unless the current TvView is tuned.
+     *
+     * @param enabled {@code true} to enable, {@code false} to disable.
+     */
+    public void setCaptionEnabled(boolean enabled) {
+        if (mSession != null) {
+            mSession.setCaptionEnabled(enabled);
+        }
+    }
+
+    /**
      * Select a track.
      * <p>
      * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the track
-     * selected in previous will be unselected.
+     * selected in previous will be unselected. Note that this method does not take any effect
+     * unless the current TvView is tuned.
      * </p>
+     *
      * @param track the track to be selected.
      * @see #getTracks()
      */
@@ -232,6 +246,8 @@
 
     /**
      * Unselect a track.
+     * <p>
+     * Note that this method does not take any effect unless the current TvView is tuned.
      *
      * @param track the track to be unselected.
      * @see #getTracks()
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 4a7c096..6a835d6a 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -54,7 +54,8 @@
 };
 
 enum {
-    EVENT_NOTIFY = 1,
+    EVENT_CALLBACK = 1,
+    EVENT_SET_CALLBACK = 2,
 };
 
 struct CryptoErrorCodes {
@@ -82,9 +83,7 @@
         JNIEnv *env, jobject thiz,
         const char *name, bool nameIsType, bool encoder)
     : mClass(NULL),
-      mObject(NULL),
-      mGeneration(1),
-      mRequestedActivityNotification(false) {
+      mObject(NULL) {
     jclass clazz = env->GetObjectClass(thiz);
     CHECK(clazz != NULL);
 
@@ -151,6 +150,18 @@
     mClass = NULL;
 }
 
+status_t JMediaCodec::setCallback(jobject cb) {
+    if (cb != NULL) {
+        if (mCallbackNotification == NULL) {
+            mCallbackNotification = new AMessage(kWhatCallbackNotify, id());
+        }
+    } else {
+        mCallbackNotification.clear();
+    }
+
+    return mCodec->setCallback(mCallbackNotification);
+}
+
 status_t JMediaCodec::configure(
         const sp<AMessage> &format,
         const sp<IGraphicBufferProducer> &bufferProducer,
@@ -173,32 +184,13 @@
 }
 
 status_t JMediaCodec::start() {
-    status_t err = mCodec->start();
-
-    if (err != OK) {
-        return err;
-    }
-
-    mActivityNotification = new AMessage(kWhatActivityNotify, id());
-    mActivityNotification->setInt32("generation", mGeneration);
-
-    requestActivityNotification();
-
-    return err;
+    return mCodec->start();
 }
 
 status_t JMediaCodec::stop() {
     mSurfaceTextureClient.clear();
 
-    status_t err = mCodec->stop();
-
-    sp<AMessage> msg = new AMessage(kWhatStopActivityNotifications, id());
-    sp<AMessage> response;
-    msg->postAndAwaitResponse(&response);
-
-    mActivityNotification.clear();
-
-    return err;
+    return mCodec->stop();
 }
 
 status_t JMediaCodec::flush() {
@@ -230,11 +222,7 @@
 }
 
 status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
-    status_t err = mCodec->dequeueInputBuffer(index, timeoutUs);
-
-    requestActivityNotification();
-
-    return err;
+    return mCodec->dequeueInputBuffer(index, timeoutUs);
 }
 
 status_t JMediaCodec::dequeueOutputBuffer(
@@ -245,8 +233,6 @@
     status_t err = mCodec->dequeueOutputBuffer(
             index, &offset, &size, &timeUs, &flags, timeoutUs);
 
-    requestActivityNotification();
-
     if (err != OK) {
         return err;
     }
@@ -274,10 +260,21 @@
     return mCodec->signalEndOfInputStream();
 }
 
-status_t JMediaCodec::getOutputFormat(JNIEnv *env, jobject *format) const {
+status_t JMediaCodec::getFormat(JNIEnv *env, bool input, jobject *format) const {
     sp<AMessage> msg;
     status_t err;
-    if ((err = mCodec->getOutputFormat(&msg)) != OK) {
+    err = input ? mCodec->getInputFormat(&msg) : mCodec->getOutputFormat(&msg);
+    if (err != OK) {
+        return err;
+    }
+
+    return ConvertMessageToMap(env, msg, format);
+}
+
+status_t JMediaCodec::getOutputFormat(JNIEnv *env, size_t index, jobject *format) const {
+    sp<AMessage> msg;
+    status_t err;
+    if ((err = mCodec->getOutputFormat(index, &msg)) != OK) {
         return err;
     }
 
@@ -309,6 +306,11 @@
 
     CHECK(orderID != NULL);
 
+    jmethodID asReadOnlyBufferID = env->GetMethodID(
+            byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+
+    CHECK(asReadOnlyBufferID != NULL);
+
     ScopedLocalRef<jclass> byteOrderClass(
             env, env->FindClass("java/nio/ByteOrder"));
 
@@ -341,6 +343,12 @@
             env->NewDirectByteBuffer(
                 buffer->base(),
                 buffer->capacity());
+        if (!input && byteBuffer != NULL) {
+            jobject readOnlyBuffer = env->CallObjectMethod(
+                    byteBuffer, asReadOnlyBufferID);
+            env->DeleteLocalRef(byteBuffer);
+            byteBuffer = readOnlyBuffer;
+        }
         if (byteBuffer == NULL) {
             env->DeleteLocalRef(nativeByteOrderObj);
             return NO_MEMORY;
@@ -363,6 +371,130 @@
     return OK;
 }
 
+status_t JMediaCodec::getBuffer(
+        JNIEnv *env, bool input, size_t index, jobject *buf) const {
+    sp<ABuffer> buffer;
+
+    status_t err =
+        input
+            ? mCodec->getInputBuffer(index, &buffer)
+            : mCodec->getOutputBuffer(index, &buffer);
+
+    if (err != OK) {
+        return err;
+    }
+
+    ScopedLocalRef<jclass> byteBufferClass(
+            env, env->FindClass("java/nio/ByteBuffer"));
+
+    CHECK(byteBufferClass.get() != NULL);
+
+    jmethodID orderID = env->GetMethodID(
+            byteBufferClass.get(),
+            "order",
+            "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
+
+    CHECK(orderID != NULL);
+
+    jmethodID asReadOnlyBufferID = env->GetMethodID(
+            byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+
+    CHECK(asReadOnlyBufferID != NULL);
+
+    jmethodID positionID = env->GetMethodID(
+            byteBufferClass.get(), "position", "(I)Ljava/nio/Buffer;");
+
+    CHECK(positionID != NULL);
+
+    jmethodID limitID = env->GetMethodID(
+            byteBufferClass.get(), "limit", "(I)Ljava/nio/Buffer;");
+
+    CHECK(limitID != NULL);
+
+    ScopedLocalRef<jclass> byteOrderClass(
+            env, env->FindClass("java/nio/ByteOrder"));
+
+    CHECK(byteOrderClass.get() != NULL);
+
+    jmethodID nativeOrderID = env->GetStaticMethodID(
+            byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
+    CHECK(nativeOrderID != NULL);
+
+    jobject nativeByteOrderObj =
+        env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID);
+    CHECK(nativeByteOrderObj != NULL);
+
+    // if this is an ABuffer that doesn't actually hold any accessible memory,
+    // use a null ByteBuffer
+    if (buffer->base() == NULL) {
+        *buf = NULL;
+        return OK;
+    }
+
+    jobject byteBuffer =
+        env->NewDirectByteBuffer(
+            buffer->base(),
+            buffer->capacity());
+    if (!input && byteBuffer != NULL) {
+        jobject readOnlyBuffer = env->CallObjectMethod(
+                byteBuffer, asReadOnlyBufferID);
+        env->DeleteLocalRef(byteBuffer);
+        byteBuffer = readOnlyBuffer;
+    }
+    if (byteBuffer == NULL) {
+        env->DeleteLocalRef(nativeByteOrderObj);
+        return NO_MEMORY;
+    }
+    jobject me = env->CallObjectMethod(
+            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 = NULL;
+
+    env->DeleteLocalRef(nativeByteOrderObj);
+    nativeByteOrderObj = NULL;
+
+    *buf = byteBuffer;
+    return OK;
+}
+
+status_t JMediaCodec::getImage(
+        JNIEnv *env, bool input, size_t index, jobject *buf) const {
+    sp<ABuffer> buffer;
+
+    status_t err =
+        input
+            ? mCodec->getInputBuffer(index, &buffer)
+            : mCodec->getOutputBuffer(index, &buffer);
+
+    if (err != OK) {
+        return err;
+    }
+
+    // if this is an ABuffer that doesn't actually hold any accessible memory,
+    // use a null ByteBuffer
+    *buf = NULL;
+    if (buffer->base() == NULL) {
+        return OK;
+    }
+
+    // check if buffer is an image
+    AString imageData;
+    if (!buffer->meta()->findString("image-data", &imageData)) {
+        return OK;
+    }
+
+    return OK;
+}
+
+
 status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const {
     AString name;
 
@@ -387,65 +519,121 @@
     }
 }
 
-void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
-    switch (msg->what()) {
-        case kWhatRequestActivityNotifications:
-        {
-            if (mRequestedActivityNotification) {
-                break;
-            }
+void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
+    int32_t arg1, arg2 = 0;
+    jobject obj = NULL;
+    CHECK(msg->findInt32("callbackID", &arg1));
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
 
-            mCodec->requestActivityNotification(mActivityNotification);
-            mRequestedActivityNotification = true;
+    switch (arg1) {
+        case MediaCodec::CB_INPUT_AVAILABLE:
+        {
+            CHECK(msg->findInt32("index", &arg2));
             break;
         }
 
-        case kWhatActivityNotify:
+        case MediaCodec::CB_OUTPUT_AVAILABLE:
         {
-            {
-                int32_t generation;
-                CHECK(msg->findInt32("generation", &generation));
+            CHECK(msg->findInt32("index", &arg2));
 
-                if (generation != mGeneration) {
-                    // stale
-                    break;
+            size_t size, offset;
+            int64_t timeUs;
+            uint32_t flags;
+            CHECK(msg->findSize("size", &size));
+            CHECK(msg->findSize("offset", &offset));
+            CHECK(msg->findInt64("timeUs", &timeUs));
+            CHECK(msg->findInt32("flags", (int32_t *)&flags));
+
+            ScopedLocalRef<jclass> clazz(
+                    env, env->FindClass("android/media/MediaCodec$BufferInfo"));
+            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V");
+            jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
+
+            obj = env->NewObject(clazz.get(), ctor);
+
+            if (obj == NULL) {
+                if (env->ExceptionCheck()) {
+                    ALOGE("Could not create MediaCodec.BufferInfo.");
+                    env->ExceptionClear();
                 }
-
-                mRequestedActivityNotification = false;
+                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                return;
             }
 
-            JNIEnv *env = AndroidRuntime::getJNIEnv();
-            env->CallVoidMethod(
-                    mObject,
-                    gFields.postEventFromNativeID,
-                    EVENT_NOTIFY,
-                    0 /* arg1 */,
-                    0 /* arg2 */,
-                    NULL /* obj */);
+            env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
+            break;
+        }
+
+        case MediaCodec::CB_ERROR:
+        {
+            int32_t err, actionCode;
+            CHECK(msg->findInt32("err", &err));
+            CHECK(msg->findInt32("actionCode", &actionCode));
+
+            // use Integer object to pass the action code
+            ScopedLocalRef<jclass> clazz(
+                    env, env->FindClass("android/media/MediaCodec$CodecException"));
+            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;)V");
+
+            AString str;
+            const char *detail = "Unknown error";
+            if (msg->findString("detail", &str)) {
+                detail = str.c_str();
+            }
+            jstring msgObj = env->NewStringUTF(detail);
+
+            obj = env->NewObject(clazz.get(), ctor, err, actionCode, msgObj);
+
+            if (obj == NULL) {
+                if (env->ExceptionCheck()) {
+                    ALOGE("Could not create CodecException object.");
+                    env->ExceptionClear();
+                }
+                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                return;
+            }
 
             break;
         }
 
-        case kWhatStopActivityNotifications:
+        case MediaCodec::CB_OUTPUT_FORMAT_CHANGED:
         {
-            uint32_t replyID;
-            CHECK(msg->senderAwaitsResponse(&replyID));
+            sp<AMessage> format;
+            CHECK(msg->findMessage("format", &format));
 
-            ++mGeneration;
-            mRequestedActivityNotification = false;
+            if (OK != ConvertMessageToMap(env, format, &obj)) {
+                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                return;
+            }
 
-            sp<AMessage> response = new AMessage;
-            response->postReply(replyID);
             break;
         }
 
         default:
             TRESPASS();
     }
+
+    env->CallVoidMethod(
+            mObject,
+            gFields.postEventFromNativeID,
+            EVENT_CALLBACK,
+            arg1,
+            arg2,
+            obj);
+
+    env->DeleteLocalRef(obj);
 }
 
-void JMediaCodec::requestActivityNotification() {
-    (new AMessage(kWhatRequestActivityNotifications, id()))->post();
+void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatCallbackNotify:
+        {
+            handleCallback(msg);
+            break;
+        }
+        default:
+            TRESPASS();
+    }
 }
 
 }  // namespace android
@@ -551,6 +739,22 @@
     return 0;
 }
 
+static void android_media_MediaCodec_native_setCallback(
+        JNIEnv *env,
+        jobject thiz,
+        jobject cb) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->setCallback(cb);
+
+    throwExceptionAsNecessary(env, err);
+}
+
 static void android_media_MediaCodec_native_configure(
         JNIEnv *env,
         jobject thiz,
@@ -908,9 +1112,9 @@
     throwExceptionAsNecessary(env, err);
 }
 
-static jobject android_media_MediaCodec_getOutputFormatNative(
-        JNIEnv *env, jobject thiz) {
-    ALOGV("android_media_MediaCodec_getOutputFormatNative");
+static jobject android_media_MediaCodec_getFormatNative(
+        JNIEnv *env, jobject thiz, jboolean input) {
+    ALOGV("android_media_MediaCodec_getFormatNative");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
@@ -920,7 +1124,30 @@
     }
 
     jobject format;
-    status_t err = codec->getOutputFormat(env, &format);
+    status_t err = codec->getFormat(env, input, &format);
+
+    if (err == OK) {
+        return format;
+    }
+
+    throwExceptionAsNecessary(env, err);
+
+    return NULL;
+}
+
+static jobject android_media_MediaCodec_getOutputFormatForIndexNative(
+        JNIEnv *env, jobject thiz, jint index) {
+    ALOGV("android_media_MediaCodec_getOutputFormatForIndexNative");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject format;
+    status_t err = codec->getOutputFormat(env, index, &format);
 
     if (err == OK) {
         return format;
@@ -957,6 +1184,58 @@
     return NULL;
 }
 
+static jobject android_media_MediaCodec_getBuffer(
+        JNIEnv *env, jobject thiz, jboolean input, jint index) {
+    ALOGV("android_media_MediaCodec_getBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject buffer;
+    status_t err = codec->getBuffer(env, input, index, &buffer);
+
+    if (err == OK) {
+        return buffer;
+    }
+
+    // if we're out of memory, an exception was already thrown
+    if (err != NO_MEMORY) {
+        throwExceptionAsNecessary(env, err);
+    }
+
+    return NULL;
+}
+
+static jobject android_media_MediaCodec_getImage(
+        JNIEnv *env, jobject thiz, jboolean input, jint index) {
+    ALOGV("android_media_MediaCodec_getImage");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject image;
+    status_t err = codec->getImage(env, input, index, &image);
+
+    if (err == OK) {
+        return image;
+    }
+
+    // if we're out of memory, an exception was already thrown
+    if (err != NO_MEMORY) {
+        throwExceptionAsNecessary(env, err);
+    }
+
+    return NULL;
+}
+
 static jobject android_media_MediaCodec_getName(
         JNIEnv *env, jobject thiz) {
     ALOGV("android_media_MediaCodec_getName");
@@ -1117,7 +1396,11 @@
 }
 
 static JNINativeMethod gMethods[] = {
-    { "release", "()V", (void *)android_media_MediaCodec_release },
+    { "native_release", "()V", (void *)android_media_MediaCodec_release },
+
+    { "native_setCallback",
+      "(Landroid/media/MediaCodec$Callback;)V",
+      (void *)android_media_MediaCodec_native_setCallback },
 
     { "native_configure",
       "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;"
@@ -1127,20 +1410,20 @@
     { "createInputSurface", "()Landroid/view/Surface;",
       (void *)android_media_MediaCodec_createInputSurface },
 
-    { "start", "()V", (void *)android_media_MediaCodec_start },
+    { "native_start", "()V", (void *)android_media_MediaCodec_start },
     { "native_stop", "()V", (void *)android_media_MediaCodec_stop },
-    { "flush", "()V", (void *)android_media_MediaCodec_flush },
+    { "native_flush", "()V", (void *)android_media_MediaCodec_flush },
 
-    { "queueInputBuffer", "(IIIJI)V",
+    { "native_queueInputBuffer", "(IIIJI)V",
       (void *)android_media_MediaCodec_queueInputBuffer },
 
-    { "queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
+    { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
       (void *)android_media_MediaCodec_queueSecureInputBuffer },
 
-    { "dequeueInputBuffer", "(J)I",
+    { "native_dequeueInputBuffer", "(J)I",
       (void *)android_media_MediaCodec_dequeueInputBuffer },
 
-    { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
+    { "native_dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
       (void *)android_media_MediaCodec_dequeueOutputBuffer },
 
     { "releaseOutputBuffer", "(IZZJ)V",
@@ -1149,12 +1432,21 @@
     { "signalEndOfInputStream", "()V",
       (void *)android_media_MediaCodec_signalEndOfInputStream },
 
-    { "getOutputFormatNative", "()Ljava/util/Map;",
-      (void *)android_media_MediaCodec_getOutputFormatNative },
+    { "getFormatNative", "(Z)Ljava/util/Map;",
+      (void *)android_media_MediaCodec_getFormatNative },
+
+    { "getOutputFormatNative", "(I)Ljava/util/Map;",
+      (void *)android_media_MediaCodec_getOutputFormatForIndexNative },
 
     { "getBuffers", "(Z)[Ljava/nio/ByteBuffer;",
       (void *)android_media_MediaCodec_getBuffers },
 
+    { "getBuffer", "(ZI)Ljava/nio/ByteBuffer;",
+      (void *)android_media_MediaCodec_getBuffer },
+
+    { "getImage", "(ZI)Landroid/media/Image;",
+      (void *)android_media_MediaCodec_getImage },
+
     { "getName", "()Ljava/lang/String;",
       (void *)android_media_MediaCodec_getName },
 
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index bf9f4ea..2e650e3 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -44,6 +44,8 @@
     void registerSelf();
     void release();
 
+    status_t setCallback(jobject cb);
+
     status_t configure(
             const sp<AMessage> &format,
             const sp<IGraphicBufferProducer> &bufferProducer,
@@ -84,11 +86,19 @@
 
     status_t signalEndOfInputStream();
 
-    status_t getOutputFormat(JNIEnv *env, jobject *format) const;
+    status_t getFormat(JNIEnv *env, bool input, jobject *format) const;
+
+    status_t getOutputFormat(JNIEnv *env, size_t index, jobject *format) const;
 
     status_t getBuffers(
             JNIEnv *env, bool input, jobjectArray *bufArray) const;
 
+    status_t getBuffer(
+            JNIEnv *env, bool input, size_t index, jobject *buf) const;
+
+    status_t getImage(
+            JNIEnv *env, bool input, size_t index, jobject *image) const;
+
     status_t getName(JNIEnv *env, jstring *name) const;
 
     status_t setParameters(const sp<AMessage> &params);
@@ -99,12 +109,11 @@
     virtual ~JMediaCodec();
 
     virtual void onMessageReceived(const sp<AMessage> &msg);
+    void handleCallback(const sp<AMessage> &msg);
 
 private:
     enum {
-        kWhatActivityNotify,
-        kWhatRequestActivityNotifications,
-        kWhatStopActivityNotifications,
+        kWhatCallbackNotify,
     };
 
     jclass mClass;
@@ -114,11 +123,7 @@
     sp<ALooper> mLooper;
     sp<MediaCodec> mCodec;
 
-    sp<AMessage> mActivityNotification;
-    int32_t mGeneration;
-    bool mRequestedActivityNotification;
-
-    void requestActivityNotification();
+    sp<AMessage> mCallbackNotification;
 
     DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
 };
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index d01f4ec..38d7e3e 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -56,6 +56,7 @@
     public static final String VIDEO_H263_AMR = "/sdcard/media_api/video/H263_56_AMRNB_6.3gp";
     public static final String VIDEO_H264_AAC = "/sdcard/media_api/video/H264_320_AAC_64.3gp";
     public static final String VIDEO_H264_AMR = "/sdcard/media_api/video/H264_320_AMRNB_6.3gp";
+    public static final String VIDEO_HEVC_AAC = "/sdcard/media_api/video/HEVC_320_AAC_128.mp4";
     public static final String VIDEO_HIGHRES_H263 = "/sdcard/media_api/video/H263_500_AMRNB_12.3gp";
     public static final String VIDEO_HIGHRES_MP4 = "/sdcard/media_api/video/H264_500_AAC_128.3gp";
     public static final String VIDEO_WEBM = "/sdcard/media_api/video/big-buck-bunny_trailer.webm";
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 7b2a20e..244b07f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -417,6 +417,21 @@
         assertTrue("H264 playback memory test", memoryResult);
     }
 
+    // Test case 3: Capture the memory usage after every 20 hevc playback
+    @LargeTest
+    public void testHEVCVideoPlaybackMemoryUsage() throws Exception {
+        boolean memoryResult = false;
+
+        mStartPid = getMediaserverPid();
+        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
+            mediaStressPlayback(MediaNames.VIDEO_HEVC_AAC);
+            getMemoryWriteToLog(i);
+            writeProcmemInfo();
+        }
+        memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT);
+        assertTrue("HEVC playback memory test", memoryResult);
+    }
+
     // Test case 4: Capture the memory usage after every 20 video only recorded
     @LargeTest
     public void testH263RecordVideoOnlyMemoryUsage() throws Exception {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
index d90a4bc..9dd2732 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
@@ -137,12 +137,12 @@
     }
 
     private static <T extends Comparable<? super T>> void assertInRange(Range<T> object, T needle) {
-        assertAction("in-range", object, needle, true, object.inRange(needle));
+        assertAction("in-range", object, needle, true, object.contains(needle));
     }
 
     private static <T extends Comparable<? super T>> void assertOutOfRange(Range<T> object,
             T needle) {
-        assertAction("out-of-range", object, needle, false, object.inRange(needle));
+        assertAction("out-of-range", object, needle, false, object.contains(needle));
     }
 
     private static <T extends Comparable<? super T>> void assertUpper(Range<T> object, T expected) {
diff --git a/media/tests/contents/media_api/HEVC_320_AAC_128.mp4 b/media/tests/contents/media_api/HEVC_320_AAC_128.mp4
new file mode 100644
index 0000000..e3e3ef0
--- /dev/null
+++ b/media/tests/contents/media_api/HEVC_320_AAC_128.mp4
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
deleted file mode 100644
index 88e8495..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
deleted file mode 100644
index 3a37b27..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
deleted file mode 100644
index b2ed8cd..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/print_button_background.xml b/packages/PrintSpooler/res/drawable/print_button_background.xml
new file mode 100644
index 0000000..7b9aea5
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/print_button_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+
+    <solid
+        android:color="#FF00E5FF">
+    </solid>
+
+    <size
+        android:width="48dp"
+        android:height="48dp">
+    </size>
+
+</shape>
+
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 01cf9c1..8896a7b 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -28,7 +28,7 @@
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:padding="16dip"
-        android:elevation="8dip"
+        android:elevation="@dimen/preview_controls_elevation"
         android:background="?android:attr/colorForegroundInverse">
 
         <Spinner
@@ -50,7 +50,7 @@
         android:paddingStart="16dip"
         android:paddingEnd="16dip"
         android:orientation="horizontal"
-        android:elevation="8dip"
+        android:elevation="@dimen/preview_controls_elevation"
         android:background="?android:attr/colorForegroundInverse">
 
         <TextView
@@ -99,278 +99,16 @@
 
     <ImageButton
         android:id="@+id/print_button"
-        style="?android:attr/buttonStyleSmall"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginEnd="16dip"
-        android:elevation="8dip"
+        android:elevation="@dimen/preview_controls_elevation"
         android:background="@drawable/print_button">
     </ImageButton>
 
     <!-- Controls -->
 
-    <LinearLayout
-        android:id="@+id/dynamic_content"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:elevation="8dip"
-        android:background="?android:attr/colorForegroundInverse">
-
-        <LinearLayout
-            android:id="@+id/draggable_content"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical">
-
-            <com.android.printspooler.widget.PrintOptionsLayout
-                android:id="@+id/options_container"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                printspooler:columnCount="@integer/print_option_column_count">
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="16dip"
-                    android:layout_marginEnd="16dip"
-                    android:orientation="vertical">
-
-                    <!-- Copies -->
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="8dip"
-                        android:layout_marginStart="12dip"
-                        android:textAppearance="?android:attr/textAppearanceSmall"
-                        android:labelFor="@+id/copies_edittext"
-                        android:text="@string/label_copies">
-                    </TextView>
-
-                    <view
-                        class="com.android.printspooler.widget.FirstFocusableEditText"
-                        android:id="@+id/copies_edittext"
-                        android:layout_width="fill_parent"
-                        android:layout_height="wrap_content"
-                        style="?android:attr/editTextStyle"
-                        android:inputType="numberDecimal">
-                    </view>
-
-                </LinearLayout>
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="16dip"
-                    android:layout_marginEnd="16dip"
-                    android:orientation="vertical">
-
-                    <!-- Paper size -->
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="8dip"
-                        android:layout_marginStart="12dip"
-                        android:textAppearance="?android:attr/textAppearanceSmall"
-                        android:labelFor="@+id/paper_size_spinner"
-                        android:text="@string/label_paper_size">
-                    </TextView>
-
-                    <Spinner
-                        android:id="@+id/paper_size_spinner"
-                        android:layout_width="fill_parent"
-                        android:layout_height="wrap_content"
-                        style="@style/PrintOptionSpinnerStyle">
-                    </Spinner>
-
-                </LinearLayout>
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="16dip"
-                    android:layout_marginEnd="16dip"
-                    android:orientation="vertical">
-
-                    <!-- Color -->
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="8dip"
-                        android:layout_marginStart="12dip"
-                        android:textAppearance="?android:attr/textAppearanceSmall"
-                        android:labelFor="@+id/color_spinner"
-                        android:text="@string/label_color">
-                    </TextView>
-
-                    <Spinner
-                        android:id="@+id/color_spinner"
-                        android:layout_width="fill_parent"
-                        android:layout_height="wrap_content"
-                        style="@style/PrintOptionSpinnerStyle">
-                    </Spinner>
-
-                </LinearLayout>
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="16dip"
-                    android:layout_marginEnd="16dip"
-                    android:orientation="vertical">
-
-                    <!-- Orientation -->
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="8dip"
-                        android:layout_marginStart="12dip"
-                        android:textAppearance="?android:attr/textAppearanceSmall"
-                        android:labelFor="@+id/orientation_spinner"
-                        android:text="@string/label_orientation">
-                    </TextView>
-
-                    <Spinner
-                        android:id="@+id/orientation_spinner"
-                        android:layout_width="fill_parent"
-                        android:layout_height="wrap_content"
-                        style="@style/PrintOptionSpinnerStyle">
-                    </Spinner>
-
-                </LinearLayout>
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="16dip"
-                    android:layout_marginEnd="16dip"
-                    android:orientation="vertical">
-
-                    <!-- Range options -->
-
-                    <TextView
-                        android:id="@+id/range_options_title"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="8dip"
-                        android:layout_marginStart="12dip"
-                        android:textAppearance="?android:attr/textAppearanceSmall"
-                        android:labelFor="@+id/range_options_spinner"
-                        android:text="@string/page_count_unknown">
-                    </TextView>
-
-                    <Spinner
-                        android:id="@+id/range_options_spinner"
-                        android:layout_width="fill_parent"
-                        android:layout_height="wrap_content"
-                        style="@style/PrintOptionSpinnerStyle">
-                    </Spinner>
-
-                </LinearLayout>
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="16dip"
-                    android:layout_marginEnd="16dip"
-                    android:orientation="vertical">
-
-                    <!-- Pages -->
-
-                    <TextView
-                        android:id="@+id/page_range_title"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="8dip"
-                        android:layout_marginStart="12dip"
-                        android:textAppearance="?android:attr/textAppearanceSmall"
-                        android:text="@string/pages_range_example"
-                        android:labelFor="@+id/page_range_edittext"
-                        android:textAllCaps="false"
-                        android:visibility="visible">
-                    </TextView>
-
-                    <view
-                        class="com.android.printspooler.widget.FirstFocusableEditText"
-                        android:id="@+id/page_range_edittext"
-                        android:layout_width="fill_parent"
-                        android:layout_height="wrap_content"
-                        android:layout_gravity="bottom|fill_horizontal"
-                        style="@style/PrintOptionEditTextStyle"
-                        android:visibility="visible"
-                        android:inputType="textNoSuggestions">
-                    </view>
-
-                </LinearLayout>
-
-            </com.android.printspooler.widget.PrintOptionsLayout>
-
-            <!-- More options -->
-
-            <LinearLayout
-                android:id="@+id/more_options_container"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:paddingStart="28dip"
-                android:paddingEnd="28dip"
-                android:orientation="vertical"
-                android:visibility="visible">
-
-                <ImageView
-                    android:layout_width="fill_parent"
-                    android:layout_height="1dip"
-                    android:layout_gravity="fill_horizontal"
-                    android:background="?android:attr/colorControlNormal"
-                    android:contentDescription="@null">
-                </ImageView>
-
-                <Button
-                    android:id="@+id/more_options_button"
-                    style="?android:attr/borderlessButtonStyle"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="fill_horizontal"
-                    android:text="@string/more_options_button"
-                    android:gravity="start|center_vertical"
-                    android:textAllCaps="false">
-                </Button>
-
-                <ImageView
-                    android:layout_width="fill_parent"
-                    android:layout_height="1dip"
-                    android:layout_gravity="fill_horizontal"
-                    android:background="?android:attr/colorControlNormal"
-                    android:contentDescription="@null">
-                </ImageView>
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-        <!-- Expand/collapse handle -->
-
-        <FrameLayout
-            android:id="@+id/expand_collapse_handle"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content">
-
-            <ImageView
-                android:id="@+id/expand_collapse_icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="8dip"
-                android:layout_marginBottom="8dip"
-                android:layout_gravity="center"
-                android:background="@drawable/ic_expand_more">
-            </ImageView>
-
-        </FrameLayout>
-
-    </LinearLayout>
+    <include layout="@layout/print_activity_controls"/>
 
     <!-- Content -->
 
diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml
new file mode 100644
index 0000000..2da0714
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml
@@ -0,0 +1,281 @@
+<?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:printspooler="http://schemas.android.com/apk/res/com.android.printspooler"
+    android:id="@+id/dynamic_content"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:elevation="@dimen/preview_controls_elevation"
+    android:background="?android:attr/colorForegroundInverse">
+
+        <LinearLayout
+         android:id="@+id/draggable_content"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:orientation="vertical">
+
+         <com.android.printspooler.widget.PrintOptionsLayout
+             android:id="@+id/options_container"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content"
+             printspooler:columnCount="@integer/print_option_column_count">
+
+             <LinearLayout
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_marginStart="16dip"
+                 android:layout_marginEnd="16dip"
+                 android:orientation="vertical">
+
+                 <!-- Copies -->
+
+                 <TextView
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_marginTop="8dip"
+                     android:layout_marginStart="12dip"
+                     android:textAppearance="?android:attr/textAppearanceSmall"
+                     android:labelFor="@+id/copies_edittext"
+                     android:text="@string/label_copies">
+                 </TextView>
+
+                 <view
+                     class="com.android.printspooler.widget.FirstFocusableEditText"
+                     android:id="@+id/copies_edittext"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content"
+                     style="?android:attr/editTextStyle"
+                     android:inputType="numberDecimal">
+                 </view>
+
+             </LinearLayout>
+
+             <LinearLayout
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_marginStart="16dip"
+                 android:layout_marginEnd="16dip"
+                 android:orientation="vertical">
+
+                 <!-- Paper size -->
+
+                 <TextView
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_marginTop="8dip"
+                     android:layout_marginStart="12dip"
+                     android:textAppearance="?android:attr/textAppearanceSmall"
+                     android:labelFor="@+id/paper_size_spinner"
+                     android:text="@string/label_paper_size">
+                 </TextView>
+
+                 <Spinner
+                     android:id="@+id/paper_size_spinner"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content"
+                     style="@style/PrintOptionSpinnerStyle">
+                 </Spinner>
+
+             </LinearLayout>
+
+             <LinearLayout
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_marginStart="16dip"
+                 android:layout_marginEnd="16dip"
+                 android:orientation="vertical">
+
+                 <!-- Color -->
+
+                 <TextView
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_marginTop="8dip"
+                     android:layout_marginStart="12dip"
+                     android:textAppearance="?android:attr/textAppearanceSmall"
+                     android:labelFor="@+id/color_spinner"
+                     android:text="@string/label_color">
+                 </TextView>
+
+                 <Spinner
+                     android:id="@+id/color_spinner"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content"
+                     style="@style/PrintOptionSpinnerStyle">
+                 </Spinner>
+
+             </LinearLayout>
+
+             <LinearLayout
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_marginStart="16dip"
+                 android:layout_marginEnd="16dip"
+                 android:orientation="vertical">
+
+                 <!-- Orientation -->
+
+                 <TextView
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_marginTop="8dip"
+                     android:layout_marginStart="12dip"
+                     android:textAppearance="?android:attr/textAppearanceSmall"
+                     android:labelFor="@+id/orientation_spinner"
+                     android:text="@string/label_orientation">
+                 </TextView>
+
+                 <Spinner
+                     android:id="@+id/orientation_spinner"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content"
+                     style="@style/PrintOptionSpinnerStyle">
+                 </Spinner>
+
+             </LinearLayout>
+
+             <LinearLayout
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_marginStart="16dip"
+                 android:layout_marginEnd="16dip"
+                 android:orientation="vertical">
+
+                 <!-- Range options -->
+
+                 <TextView
+                     android:id="@+id/range_options_title"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_marginTop="8dip"
+                     android:layout_marginStart="12dip"
+                     android:textAppearance="?android:attr/textAppearanceSmall"
+                     android:labelFor="@+id/range_options_spinner"
+                     android:text="@string/page_count_unknown">
+                 </TextView>
+
+                 <Spinner
+                     android:id="@+id/range_options_spinner"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content"
+                     style="@style/PrintOptionSpinnerStyle">
+                 </Spinner>
+
+             </LinearLayout>
+
+             <LinearLayout
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_marginStart="16dip"
+                 android:layout_marginEnd="16dip"
+                 android:orientation="vertical">
+
+                 <!-- Pages -->
+
+                 <TextView
+                     android:id="@+id/page_range_title"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_marginTop="8dip"
+                     android:layout_marginStart="12dip"
+                     android:textAppearance="?android:attr/textAppearanceSmall"
+                     android:text="@string/pages_range_example"
+                     android:labelFor="@+id/page_range_edittext"
+                     android:textAllCaps="false"
+                     android:visibility="visible">
+                 </TextView>
+
+                 <view
+                     class="com.android.printspooler.widget.FirstFocusableEditText"
+                     android:id="@+id/page_range_edittext"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content"
+                     android:layout_gravity="bottom|fill_horizontal"
+                     style="@style/PrintOptionEditTextStyle"
+                     android:visibility="visible"
+                     android:inputType="textNoSuggestions">
+                 </view>
+
+             </LinearLayout>
+
+         </com.android.printspooler.widget.PrintOptionsLayout>
+
+         <!-- More options -->
+
+         <LinearLayout
+             android:id="@+id/more_options_container"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content"
+             android:paddingStart="28dip"
+             android:paddingEnd="28dip"
+             android:orientation="vertical"
+             android:visibility="visible">
+
+             <ImageView
+                 android:layout_width="fill_parent"
+                 android:layout_height="1dip"
+                 android:layout_gravity="fill_horizontal"
+                 android:background="?android:attr/colorControlNormal"
+                 android:contentDescription="@null">
+             </ImageView>
+
+             <Button
+                 android:id="@+id/more_options_button"
+                 style="?android:attr/borderlessButtonStyle"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:layout_gravity="fill_horizontal"
+                 android:text="@string/more_options_button"
+                 android:gravity="start|center_vertical"
+                 android:textAllCaps="false">
+             </Button>
+
+             <ImageView
+                 android:layout_width="fill_parent"
+                 android:layout_height="1dip"
+                 android:layout_gravity="fill_horizontal"
+                 android:background="?android:attr/colorControlNormal"
+                 android:contentDescription="@null">
+             </ImageView>
+
+         </LinearLayout>
+
+        </LinearLayout>
+
+        <!-- Expand/collapse handle -->
+
+        <FrameLayout
+         android:id="@+id/expand_collapse_handle"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content">
+
+         <ImageView
+             android:id="@+id/expand_collapse_icon"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_marginTop="8dip"
+             android:layout_marginBottom="8dip"
+             android:layout_gravity="center"
+             android:background="@drawable/ic_expand_more">
+         </ImageView>
+
+    </FrameLayout>
+
+</LinearLayout>
+
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
index e84a32be..a4666a5 100644
--- a/packages/PrintSpooler/res/values-land/constants.xml
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -17,7 +17,11 @@
 <resources>
 
     <dimen name="printer_list_view_padding_start">48dip</dimen>
+
     <dimen name="printer_list_view_padding_end">48dip</dimen>
+
     <integer name="preview_page_per_row_count">2</integer>
 
+    <integer name="print_option_column_count">3</integer>
+
 </resources>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index a19cd65..c17c73b 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -38,4 +38,8 @@
 
     <dimen name="preview_list_padding">24dip</dimen>
 
+    <dimen name="preview_controls_elevation">8dip</dimen>
+
+    <integer name="print_option_column_count">2</integer>
+
 </resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index 63b4d96..3678cf2 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -26,7 +26,6 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.pdf.PdfRenderer;
 import android.os.AsyncTask;
-import android.os.Debug;
 import android.os.ParcelFileDescriptor;
 import android.print.PrintAttributes.MediaSize;
 import android.print.PrintAttributes.Margins;
@@ -44,7 +43,7 @@
 public final class PageContentRepository {
     private static final String LOG_TAG = "PageContentRepository";
 
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private static final int INVALID_PAGE_INDEX = -1;
 
@@ -69,7 +68,6 @@
     private RenderSpec mLastRenderSpec;
 
     private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
-
     private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
 
     private int mState;
@@ -525,7 +523,8 @@
                     * BYTES_PER_PIXEL;
             final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
                     / bitmapSizeInBytes;
-            final int halfPreloadCount = (maxCachedPageCount - (lastShownPage - firstShownPage)) /2;
+            final int halfPreloadCount = (maxCachedPageCount
+                    - (lastShownPage - firstShownPage)) / 2 - 1;
 
             final int excessFromStart;
             if (firstShownPage - halfPreloadCount < 0) {
@@ -713,36 +712,6 @@
                     break;
                 }
 
-//                if (mRenderedPage == null) {
-//                    final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
-//                            * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
-//
-//                    while (mPageContentCache.getSizeInBytes() > 0
-//                            && mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
-//                            > mPageContentCache.getMaxSizeInBytes()) {
-//                        RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
-//
-//                        // If not the right size - recycle and keep freeing space.
-//                        Bitmap bitmap = renderedPage.content.getBitmap();
-//                        if (!mRenderSpec.hasSameSize(bitmap.getWidth(), bitmap.getHeight())) {
-//                            if (DEBUG) {
-//                                Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
-//                                        + " with different size.");
-//                            }
-//                            bitmap.recycle();
-//                            continue;
-//                        }
-//
-//                        mRenderedPage = renderedPage;
-//                        bitmap.eraseColor(Color.WHITE);
-//
-//                        if (DEBUG) {
-//                            Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
-//                                    + mPageContentCache.getSizeInBytes() + " bytes");
-//                        }
-//                    }
-//                }
-
                 if (mRenderedPage == null) {
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: "
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 09ce4e1..8885a7b 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -68,54 +68,42 @@
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
     private final SparseArray<Void> mBoundPagesInAdapter = new SparseArray<>();
-
     private final SparseArray<Void> mConfirmedPagesInDocument = new SparseArray<>();
 
     private final PageClickListener mPageClickListener = new PageClickListener();
 
+    private final Context mContext;
     private final LayoutInflater mLayoutInflater;
 
-    private final Context mContext;
-
     private final ContentUpdateRequestCallback mContentUpdateRequestCallback;
-
     private final PageContentRepository mPageContentRepository;
-
     private final PreviewArea mPreviewArea;
 
-    private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
-
-    private int mSelectedPageCount;
-
     // Which document pages to be written.
     private PageRange[] mRequestedPages;
-
     // Pages written in the current file.
     private PageRange[] mWrittenPages;
-
     // Pages the user selected in the UI.
     private PageRange[] mSelectedPages;
 
-    private float mSelectedPageElevation;
+    private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
+    private int mSelectedPageCount;
 
+    private float mSelectedPageElevation;
     private float mUnselectedPageElevation;
 
     private int mPreviewPageMargin;
-
     private int mPreviewListPadding;
-
     private int mFooterHeight;
 
     private int mColumnCount;
 
     private MediaSize mMediaSize;
-
     private Margins mMinMargins;
 
     private int mState;
 
     private int mPageContentWidth;
-
     private int mPageContentHeight;
 
     public interface ContentUpdateRequestCallback {
@@ -720,17 +708,17 @@
     }
 
     public void startPreloadContent(PageRange pageRangeInAdapter) {
-//        final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
-//        final int startPageInFile = computePageIndexInFile(startPageInDocument);
-//        final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
-//        final int endPageInFile = computePageIndexInFile(endPageInDocument);
-//        if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
-//            mPageContentRepository.startPreload(startPageInFile, endPageInFile);
-//        }
+        final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
+        final int startPageInFile = computePageIndexInFile(startPageInDocument);
+        final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
+        final int endPageInFile = computePageIndexInFile(endPageInDocument);
+        if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
+            mPageContentRepository.startPreload(startPageInFile, endPageInFile);
+        }
     }
 
     public void stopPreloadContent() {
-//        mPageContentRepository.stopPreload();
+        mPageContentRepository.stopPreload();
     }
 
     private void doDestroy() {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 7359221..5ec2111 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -268,7 +268,6 @@
 
         mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
                 fileProvider);
-
         mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
                 IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
                 fileProvider, new RemotePrintDocument.DocumentObserver() {
@@ -277,23 +276,17 @@
                 finish();
             }
         }, PrintActivity.this);
-
         mProgressMessageController = new ProgressMessageController(
                 PrintActivity.this);
-
         mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
-
         mDestinationSpinnerAdapter = new DestinationAdapter();
 
         bindUi();
-
         updateOptionsUi();
 
         // Now show the updated UI to avoid flicker.
         mOptionsContent.setVisibility(View.VISIBLE);
-
         mSelectedPages = computeSelectedPages();
-
         mPrintedDocument.start();
 
         ensurePreviewUiShown();
@@ -345,7 +338,8 @@
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK
                 && event.isTracking() && !event.isCanceled()) {
-            if (mPrintPreviewController != null&&mPrintPreviewController.isOptionsOpened() && !hasErrors()) {
+            if (mPrintPreviewController != null&&mPrintPreviewController.isOptionsOpened()
+                    && !hasErrors()) {
                 mPrintPreviewController.closeOptions();
             } else {
                 cancelPrint();
@@ -364,8 +358,7 @@
 
     @Override
     public void onActionPerformed() {
-        if (mState == STATE_UPDATE_FAILED
-                && canUpdateDocument()) {
+        if (mState == STATE_UPDATE_FAILED && canUpdateDocument()) {
             updateDocument(true, true);
             ensurePreviewUiShown();
             setState(STATE_CONFIGURING);
@@ -648,7 +641,8 @@
             final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
             MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
             for (int i = 0; i < mediaSizeCount; i++) {
-                MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i).value.asPortrait();
+                MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i)
+                        .value.asPortrait();
                 if (supportedSizePortrait.equals(newMediaSizePortrait)) {
                     currAttributes.setMediaSize(newMediaSize);
                     mMediaSizeSpinner.setSelection(i);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
index 910818bf..ce47b3e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -45,22 +45,17 @@
 class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallback,
         PageAdapter.PreviewArea, EmbeddedContentContainer.OnSizeChangeListener {
 
-    private PrintActivity mActivity;
+    private final PrintActivity mActivity;
 
     private final MutexFileProvider mFileProvider;
-
     private final MyHandler mHandler;
 
     private final PageAdapter mPageAdapter;
-
     private final StaggeredGridLayoutManager mLayoutManger;
 
-    private PrintOptionsLayout mPrintOptionsLayout;
-
+    private final PrintOptionsLayout mPrintOptionsLayout;
     private final RecyclerView mRecyclerView;
-
     private final PrintContentView mContentView;
-
     private final EmbeddedContentContainer mEmbeddedContentContainer;
 
     private final PreloadController mPreloadController;
@@ -79,6 +74,7 @@
                 R.integer.preview_page_per_row_count);
 
         mLayoutManger = new StaggeredGridLayoutManager(columnCount, OrientationHelper.VERTICAL);
+
         mRecyclerView = (RecyclerView) activity.findViewById(R.id.preview_content);
         mRecyclerView.setLayoutManager(mLayoutManger);
         mRecyclerView.setAdapter(mPageAdapter);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
index bb63fb8..4d2cb6c 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
@@ -43,7 +43,6 @@
     private PageContentProvider mProvider;
 
     private MediaSize mMediaSize;
-
     private Margins mMinMargins;
 
     private boolean mContentRequested;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index b4a78e6..afdbb2a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -37,6 +37,11 @@
 public final class PrintContentView extends ViewGroup implements View.OnClickListener {
     private static final int FIRST_POINTER_ID = 0;
 
+    private static final int ALPHA_MASK = 0xff000000;
+    private static final int ALPHA_SHIFT = 24;
+
+    private static final int COLOR_MASK = 0xffffff;
+
     private final ViewDragHelper mDragger;
 
     private final int mScrimColor;
@@ -187,9 +192,9 @@
     }
 
     private int computeScrimColor() {
-        final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
+        final int baseAlpha = (mScrimColor & ALPHA_MASK) >>> ALPHA_SHIFT;
         final int adjustedAlpha = (int) (baseAlpha * (1 - mDragProgress));
-        return adjustedAlpha << 24 | (mScrimColor & 0xffffff);
+        return adjustedAlpha << ALPHA_SHIFT | (mScrimColor & COLOR_MASK);
     }
 
     private int getOpenedOptionsY() {
diff --git a/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml
new file mode 100644
index 0000000..e5737ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.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="24.0dp"
+        android:height="24.0dp"/>
+
+    <viewport
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"/>
+
+    <path
+        android:fill="@color/recents_task_view_lock_to_app_button_color"
+        android:pathData="M18.0,8.0l-1.0,0.0L17.0,6.0c0.0,-2.8 -2.2,-5.0 -5.0,-5.0C9.2,1.0 7.0,3.2 7.0,6.0l0.0,2.0L6.0,8.0c-1.1,0.0 -2.0,0.9 -2.0,2.0l0.0,10.0c0.0,1.1 0.9,2.0 2.0,2.0l12.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L20.0,10.0C20.0,8.9 19.1,8.0 18.0,8.0zM12.0,17.0c-1.1,0.0 -2.0,-0.9 -2.0,-2.0s0.9,-2.0 2.0,-2.0c1.1,0.0 2.0,0.9 2.0,2.0S13.1,17.0 12.0,17.0zM15.1,8.0L8.9,8.0L8.9,6.0c0.0,-1.7 1.4,-3.1 3.1,-3.1c1.7,0.0 3.1,1.4 3.1,3.1L15.1,8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
new file mode 100644
index 0000000..aa0b369
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.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="56dp"
+        android:height="56dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="@color/qs_detail_empty"
+        android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cancel.xml b/packages/SystemUI/res/drawable/ic_qs_cancel.xml
new file mode 100644
index 0000000..de72f13
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_cancel.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="24dp"
+        android:height="24dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:fill="#FFFFFFFF"
+        android:pathData="M24.0,4.0C12.9,4.0 4.0,12.9 4.0,24.0s8.9,20.0 20.0,20.0c11.1,0.0 20.0,-8.9 20.0,-20.0S35.1,4.0 24.0,4.0zM34.0,31.2L31.2,34.0L24.0,26.8L16.8,34.0L14.0,31.2l7.2,-7.2L14.0,16.8l2.8,-2.8l7.2,7.2l7.2,-7.2l2.8,2.8L26.8,24.0L34.0,31.2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
new file mode 100644
index 0000000..16fa30b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.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="56dp"
+        android:height="56dp"/>
+
+    <viewport
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"/>
+
+    <path
+        android:pathData="M24.0,4.0C15.0,4.0 6.7,7.0 0.0,12.0l24.0,32.0l24.0,-32.0C41.3,7.0 33.0,4.0 24.0,4.0z"
+        android:fill="@color/qs_detail_empty" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
new file mode 100644
index 0000000..d38c8a4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+     android:color="#ffdadada">
+    <item android:drawable="@color/recents_task_view_lock_to_app_button_background_color" />
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
index c5eaed9..55139fb 100644
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -17,9 +17,9 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="@dimen/qs_detail_item_height"
-    android:gravity="center_vertical"
     android:background="@drawable/btn_borderless_rect"
     android:clickable="true"
+    android:gravity="center_vertical"
     android:orientation="horizontal" >
 
     <ImageView
@@ -29,9 +29,10 @@
         android:layout_marginEnd="12dp" />
 
     <LinearLayout
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginStart="20dp"
+        android:layout_weight="1"
         android:orientation="vertical" >
 
         <TextView
@@ -48,4 +49,13 @@
             android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
     </LinearLayout>
 
+    <ImageView
+        android:id="@android:id/icon2"
+        style="@style/QSBorderlessButton"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:clickable="true"
+        android:scaleType="center"
+        android:src="@drawable/ic_qs_cancel" />
+
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
new file mode 100644
index 0000000..b64005f
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -0,0 +1,49 @@
+<?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.
+-->
+<!-- extends FrameLayout -->
+<com.android.systemui.qs.QSDetailItems xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <LinearLayout
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" />
+
+    <LinearLayout
+        android:id="@android:id/empty"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center_horizontal"
+        android:orientation="vertical" >
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="56dp"
+            android:layout_height="56dp" />
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:textAppearance="@style/TextAppearance.QS.DetailEmpty" />
+    </LinearLayout>
+
+</com.android.systemui.qs.QSDetailItems>
\ 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 2b50a95..7e8bfd32 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -62,6 +62,27 @@
             android:visibility="invisible"
             android:src="@drawable/recents_dismiss_light" />
     </com.android.systemui.recents.views.TaskBarView>
+    <FrameLayout
+        android:id="@+id/lock_to_app"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/recents_task_view_lock_to_app_button_height"
+        android:layout_gravity="center_horizontal|bottom"
+        android:background="@drawable/recents_lock_to_task_button_bg"
+        android:visibility="invisible">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_horizontal"
+            android:gravity="center"
+            android:drawableLeft="@drawable/ic_lock_to_app_24dp"
+            android:drawablePadding="8dp"
+            android:textSize="16sp"
+            android:textColor="@color/recents_task_view_lock_to_app_button_color"
+            android:text="@string/recents_lock_to_app_button_label"
+            android:fontFamily="sans-serif-medium"
+            android:singleLine="true"
+            android:textAllCaps="true" />
+    </FrameLayout>
 </com.android.systemui.recents.views.TaskView>
 
 
diff --git a/packages/SystemUI/res/layout/split_clock_view.xml b/packages/SystemUI/res/layout/split_clock_view.xml
new file mode 100644
index 0000000..d9ba35d
--- /dev/null
+++ b/packages/SystemUI/res/layout/split_clock_view.xml
@@ -0,0 +1,38 @@
+<?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
+  -->
+
+<!-- Extends LinearLayout -->
+<com.android.systemui.statusbar.policy.SplitClockView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    >
+    <TextClock
+        android:id="@+id/time_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+        />
+    <TextClock
+        android:id="@+id/am_pm_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+        />
+</com.android.systemui.statusbar.policy.SplitClockView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 8b79dbf..272904a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -98,13 +98,8 @@
         android:background="?android:attr/selectableItemBackground"
         android:enabled="false"
         >
-        <com.android.systemui.statusbar.policy.Clock
+        <include layout="@layout/split_clock_view"
             android:id="@+id/clock"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
-            systemui:amPmStyle="normal"
             />
 
         <com.android.systemui.statusbar.policy.DateView android:id="@+id/date_collapsed"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ef8302d..bae7ed7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -41,6 +41,7 @@
     <color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
     <color name="qs_tile_text">#B3FFFFFF</color><!-- 70% white -->
     <color name="qs_subhead">#66FFFFFF</color><!-- 40% white -->
+    <color name="qs_detail_empty">#24B0BEC5</color><!-- 14% blue grey 200-->
     <color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
     <color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
     <color name="status_bar_clock_color">#33FFFFFF</color>
@@ -53,13 +54,17 @@
     <!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->
     <color name="recents_task_bar_light_text_color">#ffeeeeee</color>
     <!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
-    <color name="recents_task_bar_dark_text_color">#ff333333</color>
+    <color name="recents_task_bar_dark_text_color">#cc000000</color>
     <!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. -->
     <color name="recents_task_bar_light_dismiss_color">#ffeeeeee</color>
     <!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
-    <color name="recents_task_bar_dark_dismiss_color">#ff333333</color>
+    <color name="recents_task_bar_dark_dismiss_color">#cc000000</color>
     <!-- The recents task bar highlight color. -->
     <color name="recents_task_bar_highlight_color">#28ffffff</color>
+    <!-- The lock to task button background color. -->
+    <color name="recents_task_view_lock_to_app_button_background_color">#ffe6e6e6</color>
+    <!-- The lock to task button foreground color. -->
+    <color name="recents_task_view_lock_to_app_button_color">#ff666666</color>
 
     <color name="keyguard_affordance">#ffffffff</color>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5c7dc908..94fcc23 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -127,6 +127,10 @@
     <integer name="recents_animate_task_enter_from_home_duration">275</integer>
     <!-- The animation stagger to apply to each task animation when transitioning from home. -->
     <integer name="recents_animate_task_enter_from_home_delay">10</integer>
+    <!-- The short duration when animating in/out the lock to app button. -->
+    <integer name="recents_animate_lock_to_app_button_short_duration">150</integer>
+    <!-- The long duration when animating in/out the lock to app button. -->
+    <integer name="recents_animate_lock_to_app_button_long_duration">300</integer>
     <!-- The min animation duration for animating the nav bar scrim in. -->
     <integer name="recents_nav_bar_scrim_enter_duration">400</integer>
     <!-- The animation duration for animating the removal of a task view. -->
@@ -157,5 +161,8 @@
 
     <!-- Doze: should the significant motion sensor be used as a tease signal? -->
     <bool name="doze_tease_on_significant_motion">true</bool>
+
+    <!-- Volume: time to delay dismissing the volume panel after a click is performed -->
+    <integer name="volume_panel_dismiss_delay">200</integer>
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5400326..e86aa0a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -210,6 +210,9 @@
     <!-- The amount of highlight to make on each task view. -->
     <dimen name="recents_task_view_highlight">1dp</dimen>
 
+    <!-- The height of the lock-to-app button. -->
+    <dimen name="recents_task_view_lock_to_app_button_height">48dp</dimen>
+
     <!-- The height of a task view bar. -->
     <dimen name="recents_task_bar_height">56dp</dimen>
 
@@ -232,8 +235,9 @@
     <!-- Space reserved for the cards behind the top card in the bottom stack -->
     <dimen name="bottom_stack_peek_amount">12dp</dimen>
 
-    <!-- bottom_stack_peek_amount + notification_min_height -->
-    <dimen name="min_stack_height">76dp</dimen>
+    <!-- bottom_stack_peek_amount + notification_min_height
+         + notification_collapse_second_card_padding -->
+    <dimen name="min_stack_height">84dp</dimen>
 
     <!-- The height of the area before the bottom stack in which the notifications slow down -->
     <dimen name="bottom_stack_slow_down_length">12dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 848fdf8..b0f2133 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -486,6 +486,8 @@
     <string name="quick_settings_bluetooth_multiple_devices_label">Bluetooth (<xliff:g id="number">%d</xliff:g> Devices)</string>
     <!-- QuickSettings: Bluetooth (Off) [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_off_label">Bluetooth Off</string>
+    <!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string>
     <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
     <string name="quick_settings_brightness_label">Brightness</string>
     <!-- QuickSettings: Rotation Unlocked [CHAR LIMIT=NONE] -->
@@ -522,6 +524,8 @@
     <string name="quick_settings_wifi_no_network">No Network</string>
     <!-- QuickSettings: Wifi (Off) [CHAR LIMIT=NONE] -->
     <string name="quick_settings_wifi_off_label">Wi-Fi Off</string>
+    <!-- QuickSettings: Wifi detail panel, text when there are no items [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_wifi_detail_empty_text">No saved networks available</string>
     <!-- QuickSettings: Remote display [CHAR LIMIT=NONE] -->
     <string name="quick_settings_remote_display_no_connection_label">Cast screen</string>
     <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
@@ -538,6 +542,8 @@
     <string name="quick_settings_done">Done</string>
     <!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_connected">Connected</string>
+    <!-- QuickSettings: Control panel: Label for connecting device. [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_connecting">Connecting...</string>
     <!-- QuickSettings: Tethering. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_tethering_label">Tethering</string>
     <!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] -->
@@ -563,6 +569,8 @@
     <string name="recents_empty_message">No recent apps</string>
     <!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] -->
     <string name="recents_app_info_button_label">Application Info</string>
+    <!-- Recents: The lock-to-app button. [CHAR LIMIT=NONE] -->
+    <string name="recents_lock_to_app_button_label">lock to app</string>
     <!-- Recents: Temporary string for the button in the recents search bar. [CHAR LIMIT=NONE] -->
     <string name="recents_search_bar_label">search</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f99b68b..7da6c22 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -179,6 +179,11 @@
         <item name="android:gravity">center</item>
     </style>
 
+    <style name="TextAppearance.QS.DetailEmpty">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/qs_subhead</item>
+    </style>
+
     <style name="TextAppearance.QS.Subhead">
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">sans-serif-medium</item>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
new file mode 100644
index 0000000..24c1378
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * Quick settings common detail view with line items.
+ */
+public class QSDetailItems extends FrameLayout {
+    private static final String TAG = "QSDetailItems";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Context mContext;
+    private final H mHandler = new H();
+
+    private String mTag;
+    private Callback mCallback;
+    private boolean mItemsVisible = true;
+    private LinearLayout mItems;
+    private View mEmpty;
+    private TextView mEmptyText;
+    private ImageView mEmptyIcon;
+
+    public QSDetailItems(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+        mTag = TAG;
+    }
+
+    public static QSDetailItems convertOrInflate(Context context, View convert, ViewGroup parent) {
+        if (convert instanceof QSDetailItems) {
+            return (QSDetailItems) convert;
+        }
+        return (QSDetailItems) LayoutInflater.from(context).inflate(R.layout.qs_detail_items,
+                parent, false);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mItems = (LinearLayout) findViewById(android.R.id.list);
+        mItems.setVisibility(GONE);
+        mEmpty = findViewById(android.R.id.empty);
+        mEmpty.setVisibility(GONE);
+        mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
+        mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
+    }
+
+    public void setTagSuffix(String suffix) {
+        mTag = TAG + "." + suffix;
+    }
+
+    public void setEmptyState(int icon, int text) {
+        mEmptyIcon.setImageResource(icon);
+        mEmptyText.setText(text);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (DEBUG) Log.d(mTag, "onAttachedToWindow");
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+        mCallback = null;
+    }
+
+    public void setCallback(Callback callback) {
+        mHandler.removeMessages(H.SET_CALLBACK);
+        mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
+    }
+
+    public void setItems(Item[] items) {
+        mHandler.removeMessages(H.SET_ITEMS);
+        mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget();
+    }
+
+    public void setItemsVisible(boolean visible) {
+        mHandler.removeMessages(H.SET_ITEMS_VISIBLE);
+        mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+    }
+
+    private void handleSetCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    private void handleSetItems(Item[] items) {
+        final int itemCount = items != null ? items.length : 0;
+        mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
+        mItems.setVisibility(itemCount == 0 ? GONE : VISIBLE);
+        for (int i = mItems.getChildCount() - 1; i >= itemCount; i--) {
+            mItems.removeViewAt(i);
+        }
+        for (int i = 0; i < itemCount; i++) {
+            bind(items[i], mItems.getChildAt(i));
+        }
+    }
+
+    private void handleSetItemsVisible(boolean visible) {
+        if (mItemsVisible == visible) return;
+        mItemsVisible = visible;
+        for (int i = 0; i < mItems.getChildCount(); i++) {
+            mItems.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+        }
+    }
+
+    private void bind(final Item item, View view) {
+        if (view == null) {
+            view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, this, false);
+            mItems.addView(view);
+        }
+        view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+        final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
+        iv.setImageResource(item.icon);
+        final TextView title = (TextView) view.findViewById(android.R.id.title);
+        title.setText(item.line1);
+        final TextView summary = (TextView) view.findViewById(android.R.id.summary);
+        final boolean twoLines = !TextUtils.isEmpty(item.line2);
+        summary.setVisibility(twoLines ? VISIBLE : GONE);
+        summary.setText(twoLines ? item.line2 : null);
+        view.setMinimumHeight(mContext.getResources() .getDimensionPixelSize(
+                twoLines ? R.dimen.qs_detail_item_height_twoline : R.dimen.qs_detail_item_height));
+        view.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mCallback != null) {
+                    mCallback.onDetailItemClick(item);
+                }
+            }
+        });
+        final ImageView disconnect = (ImageView) view.findViewById(android.R.id.icon2);
+        disconnect.setVisibility(item.canDisconnect ? VISIBLE : GONE);
+        disconnect.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mCallback != null) {
+                    mCallback.onDetailItemDisconnect(item);
+                }
+            }
+        });
+    }
+
+    private class H extends Handler {
+        private static final int SET_ITEMS = 1;
+        private static final int SET_CALLBACK = 2;
+        private static final int SET_ITEMS_VISIBLE = 3;
+
+        public H() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == SET_ITEMS) {
+                handleSetItems((Item[]) msg.obj);
+            } else if (msg.what == SET_CALLBACK) {
+                handleSetCallback((QSDetailItems.Callback) msg.obj);
+            } else if (msg.what == SET_ITEMS_VISIBLE) {
+                handleSetItemsVisible(msg.arg1 != 0);
+            }
+        }
+    }
+
+    public static class Item {
+        public int icon;
+        public String line1;
+        public String line2;
+        public Object tag;
+        public boolean canDisconnect;
+    }
+
+    public interface Callback {
+        void onDetailItemClick(Item item);
+        void onDetailItemDisconnect(Item item);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 36cd388..449cc1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -168,6 +168,12 @@
                     fireToggleStateChanged(state);
                 }
             }
+            @Override
+            public void onScanStateChanged(boolean state) {
+                if (mDetailRecord == r) {
+                    fireScanStateChanged(state);
+                }
+            }
         });
         final View.OnClickListener click = new View.OnClickListener() {
             @Override
@@ -195,6 +201,7 @@
             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);
             if (r.detailView == null) throw new IllegalStateException("Must return detail view");
             mDetailDoneButton.setOnClickListener(new OnClickListener() {
@@ -211,7 +218,6 @@
                     mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
                 }
             });
-            mDetailRecord = r;
             mDetailContent.removeAllViews();
             mDetail.bringToFront();
             mDetailContent.addView(r.detailView);
@@ -312,6 +318,12 @@
         }
     }
 
+    private void fireScanStateChanged(boolean state) {
+        if (mCallback != null) {
+            mCallback.onScanStateChanged(state);
+        }
+    }
+
     private class H extends Handler {
         private static final int SHOW_DETAIL = 1;
         private static final int SET_TILE_VISIBILITY = 2;
@@ -344,5 +356,6 @@
     public interface Callback {
         void onShowingDetail(QSTile.DetailAdapter detail);
         void onToggleStateChanged(boolean state);
+        void onScanStateChanged(boolean state);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 62c9d9f..fc08cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -50,7 +50,7 @@
  */
 public abstract class QSTile<TState extends State> implements Listenable {
     protected final String TAG = "QSTile." + getClass().getSimpleName();
-    protected static final boolean DEBUG = false;
+    protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
 
     protected final Host mHost;
     protected final Context mContext;
@@ -129,6 +129,10 @@
         mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
     }
 
+    public void fireScanStateChanged(boolean state) {
+        mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
+    }
+
     // call only on tile worker looper
 
     private void handleSetCallback(Callback callback) {
@@ -166,6 +170,12 @@
         }
     }
 
+    private void handleScanStateChanged(boolean state) {
+        if (mCallback != null) {
+            mCallback.onScanStateChanged(state);
+        }
+    }
+
     protected void handleUserSwitch(int newUserId) {
         handleRefreshState(null);
     }
@@ -178,6 +188,7 @@
         private static final int SHOW_DETAIL = 5;
         private static final int USER_SWITCH = 6;
         private static final int TOGGLE_STATE_CHANGED = 7;
+        private static final int SCAN_STATE_CHANGED = 8;
 
         private H(Looper looper) {
             super(looper);
@@ -208,6 +219,9 @@
                 } else if (msg.what == TOGGLE_STATE_CHANGED) {
                     name = "handleToggleStateChanged";
                     handleToggleStateChanged(msg.arg1 != 0);
+                } else if (msg.what == SCAN_STATE_CHANGED) {
+                    name = "handleScanStateChanged";
+                    handleScanStateChanged(msg.arg1 != 0);
                 }
             } catch (Throwable t) {
                 final String error = "Error in " + name;
@@ -221,6 +235,7 @@
         void onStateChanged(State state);
         void onShowDetail(boolean show);
         void onToggleStateChanged(boolean state);
+        void onScanStateChanged(boolean state);
     }
 
     public interface Host {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 7431e69..1250d21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,24 +16,33 @@
 
 package com.android.systemui.qs.tiles;
 
-import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
+import android.content.Context;
 import android.content.Intent;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice;
+
+import java.util.Set;
 
 /** Quick settings tile: Bluetooth **/
 public class BluetoothTile extends QSTile<QSTile.BooleanState>  {
     private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
 
     private final BluetoothController mController;
+    private final BluetoothDetailAdapter mDetailAdapter;
 
     public BluetoothTile(Host host) {
         super(host);
         mController = host.getBluetoothController();
+        mDetailAdapter = new BluetoothDetailAdapter();
     }
 
     @Override
@@ -42,6 +51,11 @@
     }
 
     @Override
+    public DetailAdapter getDetailAdapter() {
+        return mDetailAdapter;
+    }
+
+    @Override
     protected BooleanState newTileState() {
         return new BooleanState();
     }
@@ -63,7 +77,10 @@
 
     @Override
     protected void handleSecondaryClick() {
-        mHost.startSettingsActivity(BLUETOOTH_SETTINGS);
+        if (!mState.value) {
+            mController.setBluetoothEnabled(true);
+        }
+        showDetail(true);
     }
 
     @Override
@@ -83,7 +100,8 @@
                 state.label = mController.getLastDeviceName();
             } else if (connecting) {
                 state.iconId = R.drawable.ic_qs_bluetooth_connecting;
-                stateContentDescription = mContext.getString(R.string.accessibility_desc_connecting);
+                stateContentDescription =
+                        mContext.getString(R.string.accessibility_desc_connecting);
                 state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
             } else {
                 state.iconId = R.drawable.ic_qs_bluetooth_on;
@@ -106,5 +124,100 @@
         public void onBluetoothStateChange(boolean enabled, boolean connecting) {
             refreshState();
         }
+        @Override
+        public void onBluetoothPairedDevicesChanged() {
+            mUiHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mDetailAdapter.updateItems();
+                }
+            });
+        }
     };
+
+    private final class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+        private QSDetailItems mItems;
+
+        @Override
+        public int getTitle() {
+            return R.string.quick_settings_bluetooth_label;
+        }
+
+        @Override
+        public Boolean getToggleState() {
+            return mState.value;
+        }
+
+        @Override
+        public Intent getSettingsIntent() {
+            return BLUETOOTH_SETTINGS;
+        }
+
+        @Override
+        public void setToggleState(boolean state) {
+            mController.setBluetoothEnabled(state);
+            showDetail(false);
+        }
+
+        @Override
+        public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
+            mItems.setTagSuffix("Bluetooth");
+            mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
+                    R.string.quick_settings_bluetooth_detail_empty_text);
+            mItems.setCallback(this);
+            updateItems();
+            setItemsVisible(mState.value);
+            return mItems;
+        }
+
+        public void setItemsVisible(boolean visible) {
+            if (mItems == null) return;
+            mItems.setItemsVisible(visible);
+        }
+
+        private void updateItems() {
+            if (mItems == null) return;
+            Item[] items = null;
+            final Set<PairedDevice> devices = mController.getPairedDevices();
+            if (devices != null) {
+                items = new Item[devices.size()];
+                int i = 0;
+                for (PairedDevice device : devices) {
+                    final Item item = new Item();
+                    item.icon = R.drawable.ic_qs_bluetooth_on;
+                    item.line1 = device.name;
+                    if (device.state == PairedDevice.STATE_CONNECTED) {
+                        item.icon = R.drawable.ic_qs_bluetooth_connected;
+                        item.line2 = mContext.getString(R.string.quick_settings_connected);
+                        item.canDisconnect = true;
+                    } else if (device.state == PairedDevice.STATE_CONNECTING) {
+                        item.icon = R.drawable.ic_qs_bluetooth_connecting;
+                        item.line2 = mContext.getString(R.string.quick_settings_connecting);
+                    }
+                    item.tag = device;
+                    items[i++] = item;
+                }
+            }
+            mItems.setItems(items);
+        }
+
+        @Override
+        public void onDetailItemClick(Item item) {
+            if (item == null || item.tag == null) return;
+            final PairedDevice device = (PairedDevice) item.tag;
+            if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) {
+                mController.connect(device);
+            }
+        }
+
+        @Override
+        public void onDetailItemDisconnect(Item item) {
+            if (item == null || item.tag == null) return;
+            final PairedDevice device = (PairedDevice) item.tag;
+            if (device != null) {
+                mController.disconnect(device);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 2876607..900c7b26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -21,15 +21,12 @@
 import android.content.res.Resources;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.SignalTileView;
@@ -40,7 +37,6 @@
 /** Quick settings tile: Wifi **/
 public class WifiTile extends QSTile<QSTile.SignalState> {
     private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
-    private static final int MAX_ITEMS = 4; // TODO temporary visual restriction
 
     private final NetworkController mController;
     private final WifiDetailAdapter mDetailAdapter;
@@ -66,7 +62,6 @@
         if (listening) {
             mController.addNetworkSignalChangedCallback(mCallback);
             mController.addAccessPointCallback(mDetailAdapter);
-            mController.scanForAccessPoints();
         } else {
             mController.removeNetworkSignalChangedCallback(mCallback);
             mController.removeAccessPointCallback(mDetailAdapter);
@@ -108,7 +103,7 @@
         boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null);
         boolean enabledChanging = state.enabled != cb.enabled;
         if (enabledChanging) {
-            mDetailAdapter.postUpdateItems();
+            mDetailAdapter.setItemsVisible(cb.enabled);
             fireToggleStateChanged(cb.enabled);
         }
         state.enabled = cb.enabled;
@@ -204,9 +199,9 @@
     };
 
     private final class WifiDetailAdapter implements DetailAdapter,
-            NetworkController.AccessPointCallback {
+            NetworkController.AccessPointCallback, QSDetailItems.Callback {
 
-        private LinearLayout mItems;
+        private QSDetailItems mItems;
         private AccessPoint[] mAccessPoints;
 
         @Override
@@ -232,66 +227,67 @@
 
         @Override
         public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            if (convertView != null) return convertView;
-            mItems = new LinearLayout(context);
-            mItems.setOrientation(LinearLayout.VERTICAL);
+            if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null));
+            mAccessPoints = null;
+            mController.scanForAccessPoints();
+            fireScanStateChanged(true);
+            mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
+            mItems.setTagSuffix("Wifi");
+            mItems.setCallback(this);
+            mItems.setEmptyState(R.drawable.ic_qs_wifi_detail_empty,
+                    R.string.quick_settings_wifi_detail_empty_text);
             updateItems();
+            setItemsVisible(mState.enabled);
             return mItems;
         }
 
         @Override
         public void onAccessPointsChanged(final AccessPoint[] accessPoints) {
-            mUiHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mAccessPoints = accessPoints;
-                    updateItems();
-                }
-            });
+            mAccessPoints = accessPoints;
+            updateItems();
+            if (accessPoints != null && accessPoints.length > 0) {
+                fireScanStateChanged(false);
+            }
         }
 
-        public void postUpdateItems() {
-            mUiHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    updateItems();
-                }
-            });
+        @Override
+        public void onDetailItemClick(Item item) {
+            if (item == null || item.tag == null) return;
+            final AccessPoint ap = (AccessPoint) item.tag;
+            if (!ap.isConnected) {
+                mController.connect(ap);
+            }
+            showDetail(false);
+        }
+
+        @Override
+        public void onDetailItemDisconnect(Item item) {
+            // noop
+        }
+
+        public void setItemsVisible(boolean visible) {
+            if (mItems == null) return;
+            mItems.setItemsVisible(visible);
         }
 
         private void updateItems() {
             if (mItems == null) return;
-            mItems.removeAllViews();
-            if (mAccessPoints == null || mAccessPoints.length == 0 || !mState.enabled) return;
-            for (int i = 0; i < mAccessPoints.length; i++) {
-                final AccessPoint ap = mAccessPoints[i];
-                if (ap == null) continue;
-                final View item = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item,
-                        mItems, false);
-                final ImageView iv = (ImageView) item.findViewById(android.R.id.icon);
-                iv.setImageResource(ap.iconId);
-                final TextView title = (TextView) item.findViewById(android.R.id.title);
-                title.setText(ap.ssid);
-                final TextView summary = (TextView) item.findViewById(android.R.id.summary);
-                if (ap.isConnected) {
-                    item.setMinimumHeight(mContext.getResources()
-                            .getDimensionPixelSize(R.dimen.qs_detail_item_height_twoline));
-                    summary.setText(R.string.quick_settings_connected);
-                } else {
-                    summary.setVisibility(View.GONE);
-                }
-                item.setOnClickListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        if (!ap.isConnected) {
-                            mController.connect(ap);
-                        }
-                        showDetail(false);
+            Item[] items = null;
+            if (mAccessPoints != null) {
+                items = new Item[mAccessPoints.length];
+                for (int i = 0; i < mAccessPoints.length; i++) {
+                    final AccessPoint ap = mAccessPoints[i];
+                    final Item item = new Item();
+                    item.tag = ap;
+                    item.icon = ap.iconId;
+                    item.line1 = ap.ssid;
+                    if (ap.isConnected) {
+                        item.line2 = mContext.getString(R.string.quick_settings_connected);
                     }
-                });
-                mItems.addView(item);
-                if (mItems.getChildCount() == MAX_ITEMS) break;
+                    items[i] = item;
+                }
             }
+            mItems.setItems(items);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
index 1cfc892..59f7a80 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
@@ -142,6 +142,7 @@
                 mFadeMatrix.setScale(1, fadeHeight * topFadeStrength);
                 mFadeMatrix.postTranslate(left, top);
                 mFade.setLocalMatrix(mFadeMatrix);
+                mFadePaint.setShader(mFade);
                 canvas.drawRect(left, top, right, top + length, mFadePaint);
 
                 if (mBlackPaint == null) {
@@ -157,6 +158,7 @@
                 mFadeMatrix.postRotate(180);
                 mFadeMatrix.postTranslate(left, bottom);
                 mFade.setLocalMatrix(mFadeMatrix);
+                mFadePaint.setShader(mFade);
                 canvas.drawRect(left, bottom - length, right, bottom, mFadePaint);
             }
 
@@ -165,6 +167,7 @@
                 mFadeMatrix.postRotate(-90);
                 mFadeMatrix.postTranslate(left, top);
                 mFade.setLocalMatrix(mFadeMatrix);
+                mFadePaint.setShader(mFade);
                 canvas.drawRect(left, top, left + length, bottom, mFadePaint);
             }
 
@@ -173,6 +176,7 @@
                 mFadeMatrix.postRotate(90);
                 mFadeMatrix.postTranslate(right, top);
                 mFade.setLocalMatrix(mFadeMatrix);
+                mFadePaint.setShader(mFade);
                 canvas.drawRect(right - length, top, right, bottom, mFadePaint);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 0c28488..a9a606f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -23,12 +23,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.view.View;
 import com.android.systemui.R;
@@ -70,6 +68,7 @@
     // Recents service binding
     Handler mHandler;
     boolean mBootCompleted = false;
+    boolean mStartAnimationTriggered = false;
 
     // Task launching
     RecentsConfiguration mConfig;
@@ -254,6 +253,7 @@
      * Creates the activity options for a unknown state->recents transition.
      */
     ActivityOptions getUnknownTransitionActivityOptions() {
+        mStartAnimationTriggered = false;
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_unknown_enter,
                 R.anim.recents_from_unknown_exit, mHandler, this);
@@ -263,6 +263,7 @@
      * Creates the activity options for a home->recents transition.
      */
     ActivityOptions getHomeTransitionActivityOptions() {
+        mStartAnimationTriggered = false;
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_launcher_enter,
                 R.anim.recents_from_launcher_exit, mHandler, this);
@@ -281,6 +282,7 @@
             // Take the full screenshot
             sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
             if (sLastScreenshot != null) {
+                mStartAnimationTriggered = false;
                 return ActivityOptions.makeCustomAnimation(mContext,
                         R.anim.recents_from_app_enter,
                         R.anim.recents_from_app_exit, mHandler, this);
@@ -304,6 +306,7 @@
                 c.setBitmap(null);
                 // Recycle the old thumbnail
                 firstThumbnail.recycle();
+                mStartAnimationTriggered = false;
                 return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
                         thumbnail, toTaskRect.left, toTaskRect.top, this);
             }
@@ -317,6 +320,11 @@
     Rect getThumbnailTransitionRect(int runningTaskId) {
         // Get the stack of tasks that we are animating into
         TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy);
+        if (stack.getTaskCount() == 0) {
+            return new Rect();
+        }
+
+        // Get the stack
         TaskStackView tsv = new TaskStackView(mContext, stack);
         TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
         tsv.computeRects(mTaskStackBounds.width(), mTaskStackBounds.height() - mStatusBarHeight, 0, 0);
@@ -446,9 +454,12 @@
     @Override
     public void onAnimationStarted() {
         // Notify recents to start the enter animation
-        Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
-        intent.setPackage(mContext.getPackageName());
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcast(intent);
+        if (!mStartAnimationTriggered) {
+            Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
+            intent.setPackage(mContext.getPackageName());
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendBroadcast(intent);
+            mStartAnimationTriggered = true;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index d1efb57..56de0be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -48,6 +48,7 @@
 import com.android.systemui.recents.views.SystemBarScrimViews;
 import com.android.systemui.recents.views.ViewAnimation;
 
+import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 
@@ -125,7 +126,7 @@
         }
     }
 
-    // Broadcast receiver to handle messages from our RecentsService
+    // Broadcast receiver to handle messages from AlternateRecentsComponent
     final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -458,11 +459,6 @@
         filter.addAction(ACTION_START_ENTER_ANIMATION);
         registerReceiver(mServiceBroadcastReceiver, filter);
 
-        // Start listening for widget package changes if there is one bound
-        if (mConfig.searchBarAppWidgetId >= 0) {
-            mAppWidgetHost.startListening(this);
-        }
-
         mVisible = true;
     }
 
@@ -473,6 +469,22 @@
                     Console.AnsiRed);
         }
         super.onResume();
+
+        // Start listening for widget package changes if there is one bound, post it since we don't
+        // want it stalling the startup
+        if (mConfig.searchBarAppWidgetId >= 0) {
+            final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> callback =
+                    new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(this);
+            mRecentsView.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks cb = callback.get();
+                    if (cb != null) {
+                        mAppWidgetHost.startListening(cb);
+                    }
+                }
+            }, 1);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index b039485..e62d989 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -92,6 +92,11 @@
     public int taskBarExitAnimDuration;
     public int taskBarDismissDozeDelaySeconds;
 
+    /** Lock to app */
+    public int taskViewLockToAppButtonHeight;
+    public int taskViewLockToAppShortAnimDuration;
+    public int taskViewLockToAppLongAnimDuration;
+
     /** Nav bar scrim */
     public int navBarScrimEnterDuration;
 
@@ -226,6 +231,14 @@
         taskBarDismissDozeDelaySeconds =
                 res.getInteger(R.integer.recents_task_bar_dismiss_delay_seconds);
 
+        // Lock to app
+        taskViewLockToAppButtonHeight =
+                res.getDimensionPixelSize(R.dimen.recents_task_view_lock_to_app_button_height);
+        taskViewLockToAppShortAnimDuration =
+                res.getInteger(R.integer.recents_animate_lock_to_app_button_short_duration);
+        taskViewLockToAppLongAnimDuration =
+                res.getInteger(R.integer.recents_animate_lock_to_app_button_long_duration);
+
         // Nav bar scrim
         navBarScrimEnterDuration =
                 res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
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 05c0f58..b8beda6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -17,8 +17,10 @@
 package com.android.systemui.recents.misc;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.app.SearchManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
@@ -66,6 +68,7 @@
     final static String TAG = "SystemServicesProxy";
 
     ActivityManager mAm;
+    IActivityManager mIam;
     AppWidgetManager mAwm;
     PackageManager mPm;
     IPackageManager mIpm;
@@ -83,6 +86,7 @@
     /** Private constructor */
     public SystemServicesProxy(Context context) {
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        mIam = ActivityManagerNative.getDefault();
         mAwm = AppWidgetManager.getInstance(context);
         mPm = context.getPackageManager();
         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -407,6 +411,17 @@
     }
 
     /**
+     * Locks the current task.
+     */
+    public void lockCurrentTask() {
+        if (mIam == null) return;
+
+        try {
+            mIam.startLockTaskModeOnCurrent();
+        } catch (RemoteException e) {}
+    }
+
+    /**
      * Takes a screenshot of the current surface.
      */
     public Bitmap takeScreenshot() {
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 29f1440..854ea1c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -396,7 +396,7 @@
         Resources res = context.getResources();
         ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
         TaskStack stack = new TaskStack();
-        SpaceNode root = new SpaceNode(context);
+        SpaceNode root = new SpaceNode();
         root.setStack(stack);
 
         // Get the recent tasks
@@ -428,8 +428,9 @@
             boolean isForemostTask = (i == (taskCount - 1));
 
             // Create a new task
-            Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
-                    activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime);
+            Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, t.affiliatedTaskId,
+                    activityLabel, activityIcon, activityColor, t.userId, t.firstActiveTime,
+                    t.lastActiveTime, (i == (taskCount - 1)));
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
@@ -522,8 +523,8 @@
             ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
             if (info == null) continue;
 
-            stack.addTask(new Task(t.persistentId, true, t.baseIntent, null, null, 0, 0,
-                    t.firstActiveTime, t.lastActiveTime));
+            stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, null,
+                    null, 0, 0, t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
         }
         stack.createSimulatedAffiliatedGroupings();
         return stack;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
index 20be415..831698a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.recents.model;
 
-import android.content.Context;
 import android.graphics.Rect;
 
 import java.util.ArrayList;
@@ -35,15 +34,13 @@
         public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
     }
 
-    Context mContext;
-
     SpaceNode mStartNode;
     SpaceNode mEndNode;
 
     TaskStack mStack;
 
-    public SpaceNode(Context context) {
-        mContext = context;
+    public SpaceNode() {
+        // Do nothing
     }
 
     /** Sets the current stack for this space node */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 002395f..88e9f40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -79,6 +79,7 @@
 
     public TaskKey key;
     public TaskGrouping group;
+    public int taskAffiliation;
     public Drawable applicationIcon;
     public Drawable activityIcon;
     public String activityLabel;
@@ -86,6 +87,7 @@
     public int colorPrimaryGreyscale;
     public Bitmap thumbnail;
     public boolean isActive;
+    public boolean canLockToTask;
     public int userId;
 
     TaskCallbacks mCb;
@@ -94,15 +96,17 @@
         // Only used by RecentsService for task rect calculations.
     }
 
-    public Task(int id, boolean isActive, Intent intent, String activityTitle,
-                Drawable activityIcon, int colorPrimary, int userId, long firstActiveTime,
-                long lastActiveTime) {
+    public Task(int id, boolean isActive, Intent intent, int taskAffiliation, String activityTitle,
+                Drawable activityIcon, int colorPrimary, int userId,
+                long firstActiveTime, long lastActiveTime, boolean canLockToTask) {
         this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
+        this.taskAffiliation = taskAffiliation;
         this.activityLabel = activityTitle;
         this.activityIcon = activityIcon;
         this.colorPrimary = colorPrimary;
         this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
         this.isActive = isActive;
+        this.canLockToTask = canLockToTask;
         this.userId = userId;
     }
 
@@ -148,7 +152,7 @@
     public String toString() {
         String groupAffiliation = "no group";
         if (group != null) {
-            groupAffiliation = group.affiliation;
+            groupAffiliation = Integer.toString(group.affiliation);
         }
         return "Task (" + groupAffiliation + "): " + key.baseIntent.getComponent().getPackageName() +
                 " [" + super.toString() + "]";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
index 7793549..9791038 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
@@ -6,20 +6,21 @@
 /** Represents a grouping of tasks witihin a stack. */
 public class TaskGrouping {
 
-    String affiliation;
+    int affiliation;
     long latestActiveTimeInGroup;
 
-    ArrayList<Task.TaskKey> mTasks = new ArrayList<Task.TaskKey>();
-    HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<Task.TaskKey, Integer>();
+    Task.TaskKey mFrontMostTaskKey;
+    ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>();
+    HashMap<Task.TaskKey, Integer> mTaskKeyIndices = new HashMap<Task.TaskKey, Integer>();
 
     /** Creates a group with a specified affiliation. */
-    public TaskGrouping(String affiliation) {
+    public TaskGrouping(int affiliation) {
         this.affiliation = affiliation;
     }
 
     /** Adds a new task to this group. */
     void addTask(Task t) {
-        mTasks.add(t.key);
+        mTaskKeys.add(t.key);
         if (t.key.lastActiveTime > latestActiveTimeInGroup) {
             latestActiveTimeInGroup = t.key.lastActiveTime;
         }
@@ -29,11 +30,11 @@
 
     /** Removes a task from this group. */
     void removeTask(Task t) {
-        mTasks.remove(t.key);
+        mTaskKeys.remove(t.key);
         latestActiveTimeInGroup = 0;
-        int taskCount = mTasks.size();
+        int taskCount = mTaskKeys.size();
         for (int i = 0; i < taskCount; i++) {
-            long lastActiveTime = mTasks.get(i).lastActiveTime;
+            long lastActiveTime = mTaskKeys.get(i).lastActiveTime;
             if (lastActiveTime > latestActiveTimeInGroup) {
                 latestActiveTimeInGroup = lastActiveTime;
             }
@@ -44,24 +45,31 @@
 
     /** Gets the front task */
     public boolean isFrontMostTask(Task t) {
-        return t.key.equals(mTasks.get(mTasks.size() - 1));
+        return (t.key == mFrontMostTaskKey);
     }
 
     /** Finds the index of a given task in a group. */
     public int indexOf(Task t) {
-        return mTaskIndices.get(t.key);
+        return mTaskKeyIndices.get(t.key);
     }
 
     /** Returns the number of tasks in this group. */
-    public int getTaskCount() { return mTasks.size(); }
+    public int getTaskCount() { return mTaskKeys.size(); }
 
     /** Updates the mapping of tasks to indices. */
     private void updateTaskIndices() {
-        mTaskIndices.clear();
-        int taskCount = mTasks.size();
+        if (mTaskKeys.isEmpty()) {
+            mFrontMostTaskKey = null;
+            mTaskKeyIndices.clear();
+            return;
+        }
+
+        mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1);
+        mTaskKeyIndices.clear();
+        int taskCount = mTaskKeys.size();
         for (int i = 0; i < taskCount; i++) {
-            Task.TaskKey k = mTasks.get(i);
-            mTaskIndices.put(k, i);
+            Task.TaskKey k = mTaskKeys.get(i);
+            mTaskKeyIndices.put(k, i);
         }
     }
 }
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 6bc74a7..7dd15a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -150,7 +150,7 @@
         /* Notifies when a task has been added to the stack */
         public void onStackTaskAdded(TaskStack stack, Task t);
         /* Notifies when a task has been removed from the stack */
-        public void onStackTaskRemoved(TaskStack stack, Task t);
+        public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
         /** Notifies when the stack was filtered */
         public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
         /** Notifies when the stack was un-filtered */
@@ -170,11 +170,14 @@
         }
     }
 
+    // The task offset to apply to a task id as a group affiliation
+    static final int IndividualTaskIdOffset = 1 << 16;
+
     FilteredTaskList mTaskList = new FilteredTaskList();
     TaskStackCallbacks mCb;
 
     ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
-    HashMap<String, TaskGrouping> mAffinitiesGroups = new HashMap<String, TaskGrouping>();
+    HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
 
     /** Sets the callbacks for this task stack */
     public void setCallbacks(TaskStackCallbacks cb) {
@@ -200,9 +203,15 @@
             if (group.getTaskCount() == 0) {
                 removeGroup(group);
             }
+            // Update the lock-to-app state
+            Task newFrontMostTask = getFrontMostTask();
+            t.canLockToTask = false;
+            if (newFrontMostTask != null) {
+                newFrontMostTask.canLockToTask = true;
+            }
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t);
+                mCb.onStackTaskRemoved(this, t, newFrontMostTask);
             }
         }
     }
@@ -223,7 +232,7 @@
             }
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t);
+                mCb.onStackTaskRemoved(this, t, null);
             }
         }
         mTaskList.set(tasks);
@@ -236,6 +245,7 @@
 
     /** Gets the front task */
     public Task getFrontMostTask() {
+        if (mTaskList.size() == 0) return null;
         return mTaskList.getTasks().get(mTaskList.size() - 1);
     }
 
@@ -303,7 +313,7 @@
     }
 
     /** Returns the group with the specified affiliation. */
-    public TaskGrouping getGroupWithAffiliation(String affiliation) {
+    public TaskGrouping getGroupWithAffiliation(int affiliation) {
         return mAffinitiesGroups.get(affiliation);
     }
 
@@ -325,9 +335,9 @@
             NamedCounter counter = new NamedCounter("task-group", "");
             int taskCount = tasks.size();
             String prevPackage = "";
-            String prevAffiliation = "";
+            int prevAffiliation = -1;
             Random r = new Random();
-            int groupCountDown = 1000;
+            int groupCountDown = 5;
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
                 String packageName = t.key.baseIntent.getComponent().getPackageName();
@@ -337,12 +347,12 @@
                     group = getGroupWithAffiliation(prevAffiliation);
                     groupCountDown--;
                 } else {
-                    String affiliation = counter.nextName();
+                    int affiliation = IndividualTaskIdOffset + t.key.id;
                     group = new TaskGrouping(affiliation);
                     addGroup(group);
                     prevAffiliation = affiliation;
                     prevPackage = packageName;
-                    groupCountDown = 1000;
+                    groupCountDown = 5;
                 }
                 group.addTask(t);
                 taskMap.put(t.key, t);
@@ -361,13 +371,13 @@
             int groupCount = mGroups.size();
             for (int i = 0; i < groupCount; i++) {
                 TaskGrouping group = mGroups.get(i);
-                Collections.sort(group.mTasks, new Comparator<Task.TaskKey>() {
+                Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
                     @Override
                     public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
                         return (int) (taskKey.firstActiveTime - taskKey2.firstActiveTime);
                     }
                 });
-                ArrayList<Task.TaskKey> groupTasks = group.mTasks;
+                ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
                 int groupTaskCount = groupTasks.size();
                 for (int j = 0; j < groupTaskCount; j++) {
                     tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
@@ -376,14 +386,20 @@
             }
             mTaskList.set(tasks);
         } else {
-            // Create a group per task
-            NamedCounter counter = new NamedCounter("task-group", "");
+            // Create the task groups
             ArrayList<Task> tasks = mTaskList.getTasks();
             int taskCount = tasks.size();
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
-                TaskGrouping group = new TaskGrouping(counter.nextName());
-                addGroup(group);
+                TaskGrouping group;
+                int affiliation = t.taskAffiliation > 0 ? t.taskAffiliation :
+                        IndividualTaskIdOffset + t.key.id;
+                if (mAffinitiesGroups.containsKey(affiliation)) {
+                    group = getGroupWithAffiliation(affiliation);
+                } else {
+                    group = new TaskGrouping(affiliation);
+                    addGroup(group);
+                }
                 group.addTask(t);
             }
         }
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 85afb32..99b012e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -34,9 +34,10 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
-import com.android.systemui.recents.misc.Console;
 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 com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.SpaceNode;
@@ -144,7 +145,7 @@
                             Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
                                     "Found focused Task");
                         }
-                        onTaskViewClicked(stackView, tv, stack, task);
+                        onTaskViewClicked(stackView, tv, stack, task, false);
                         return true;
                     }
                 }
@@ -180,7 +181,7 @@
                             tv = stv;
                         }
                     }
-                    onTaskViewClicked(stackView, tv, stack, task);
+                    onTaskViewClicked(stackView, tv, stack, task, false);
                     return true;
                 }
             }
@@ -431,7 +432,7 @@
 
     @Override
     public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
-                                  final TaskStack stack, final Task task) {
+                                  final TaskStack stack, final Task task, final boolean lockToTask) {
         // Notify any callbacks of the launching of a new task
         if (mCb != null) {
             mCb.onTaskViewClicked();
@@ -456,6 +457,8 @@
         }
 
         // Compute the thumbnail to scale up from
+        final SystemServicesProxy ssp =
+                RecentsTaskLoader.getInstance().getSystemServicesProxy();
         ActivityOptions opts = null;
         int thumbnailWidth = transform.rect.width();
         int thumbnailHeight = transform.rect.height();
@@ -469,8 +472,26 @@
                     new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
                     new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
             c.setBitmap(null);
+            ActivityOptions.OnAnimationStartedListener animStartedListener = null;
+            if (lockToTask) {
+                animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                    boolean mTriggered = false;
+                    @Override
+                    public void onAnimationStarted() {
+                        if (!mTriggered) {
+                            postDelayed(new Runnable() {
+                                @Override
+                                public void run() {
+                                    ssp.lockCurrentTask();
+                                }
+                            }, 350);
+                            mTriggered = true;
+                        }
+                    }
+                };
+            }
             opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
-                    b, offsetX, offsetY);
+                    b, offsetX, offsetY, animStartedListener);
         }
 
         final ActivityOptions launchOpts = opts;
@@ -496,8 +517,12 @@
                         UserHandle taskUser = new UserHandle(task.userId);
                         if (launchOpts != null) {
                             getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser);
+
                         } else {
                             getContext().startActivityAsUser(i, taskUser);
+                            if (lockToTask) {
+                                ssp.lockCurrentTask();
+                            }
                         }
                     } catch (ActivityNotFoundException anfe) {
                         Console.logError(getContext(), "Could not start Activity");
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 f38c637..599c590 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -51,11 +51,12 @@
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
         TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
-        View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
+        RecentsPackageMonitor.PackageCallbacks {
 
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
-        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
+                                      boolean lockToTask);
         public void onTaskViewAppInfoClicked(Task t);
         public void onTaskViewDismissed(Task t);
         public void onAllTaskViewsDismissed();
@@ -734,7 +735,8 @@
         for (int i = 0; i < childCount; i++) {
             TaskView t = (TaskView) getChildAt(i);
             t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
+                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
+                            mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -766,21 +768,36 @@
             TaskView t = (TaskView) getChildAt(i);
             t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
                     mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
-                    mStackAlgorithm.mTaskRect.height());
+                    mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
         }
 
         if (mAwaitingFirstLayout) {
             // Mark that we have completely the first layout
             mAwaitingFirstLayout = false;
 
+            // Find the target task with the specified id
+            ArrayList<Task> tasks = mStack.getTasks();
+            Task targetTask = null;
+            int targetTaskId = mConfig.launchedToTaskId;
+            if (targetTaskId != -1) {
+                int taskCount = tasks.size();
+                for (int i = 0; i < taskCount; i++) {
+                    Task t = tasks.get(i);
+                    if (t.key.id == targetTaskId) {
+                        targetTask = t;
+                        break;
+                    }
+                }
+            }
+
             // Prepare the first view for its enter animation
             int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
             int offscreenY = mStackAlgorithm.mRect.bottom -
                     (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
             for (int i = childCount - 1; i >= 0; i--) {
                 TaskView tv = (TaskView) getChildAt(i);
-                tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
-                        offscreenY, mStackAlgorithm.mTaskRect);
+                tv.prepareEnterRecentsAnimation(tv.getTask() == targetTask, offsetTopAlign,
+                        offscreenY);
             }
 
             // If the enter animation started already and we haven't completed a layout yet, do the
@@ -809,41 +826,40 @@
         }
 
         if (mStack.getTaskCount() > 0) {
-            if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
-                // Find the target task with the specified id
-                ArrayList<Task> tasks = mStack.getTasks();
-                Task targetTask = null;
-                int targetTaskId = mConfig.launchedToTaskId;
-                if (targetTaskId != -1) {
-                    int taskCount = tasks.size();
-                    for (int i = 0; i < taskCount; i++) {
-                        Task t = tasks.get(i);
-                        if (t.key.id == targetTaskId) {
-                            targetTask = t;
-                            break;
-                        }
+            // Find the target task with the specified id
+            ArrayList<Task> tasks = mStack.getTasks();
+            Task targetTask = null;
+            int targetTaskId = mConfig.launchedToTaskId;
+            if (targetTaskId != -1) {
+                int taskCount = tasks.size();
+                for (int i = 0; i < taskCount; i++) {
+                    Task t = tasks.get(i);
+                    if (t.key.id == targetTaskId) {
+                        targetTask = t;
+                        break;
                     }
                 }
+            }
 
-                // Find the group and task index of the target task
-                if (targetTask != null) {
-                    ctx.targetTaskTransform = new TaskViewTransform();
-                    mStackAlgorithm.getStackTransform(targetTask, getStackScroll(), ctx.targetTaskTransform);
-                    Rect taskStackBounds = new Rect();
-                    mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
-                    ctx.targetTaskTransform.rect.offset(taskStackBounds.left, taskStackBounds.top);
-                }
+            // Find the transform for the target task
+            if (targetTask != null) {
+                ctx.targetTaskTransform = new TaskViewTransform();
+                mStackAlgorithm.getStackTransform(targetTask, getStackScroll(), ctx.targetTaskTransform);
+                Rect taskStackBounds = new Rect();
+                mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
+                ctx.targetTaskTransform.rect.offset(taskStackBounds.left, taskStackBounds.top);
             }
 
             // Animate all the task views into view
             int childCount = getChildCount();
             for (int i = childCount - 1; i >= 0; i--) {
                 TaskView tv = (TaskView) getChildAt(i);
+                Task task = tv.getTask();
                 ctx.currentTaskTransform = new TaskViewTransform();
                 ctx.currentStackViewIndex = i;
                 ctx.currentStackViewCount = childCount;
-                ctx.isCurrentTaskFrontMost = (i == (getChildCount() - 1));
-                mStackAlgorithm.getStackTransform(tv.getTask(), getStackScroll(), ctx.currentTaskTransform);
+                ctx.isCurrentTaskLaunchTarget = (task == targetTask);
+                mStackAlgorithm.getStackTransform(task, getStackScroll(), ctx.currentTaskTransform);
                 tv.startEnterRecentsAnimation(ctx);
             }
 
@@ -889,11 +905,6 @@
         mUIDozeTrigger.poke();
     }
 
-    /** Disables handling touch on this task view. */
-    void setTouchOnTaskView(TaskView tv, boolean enabled) {
-        tv.setOnClickListener(enabled ? this : null);
-    }
-
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
@@ -905,25 +916,33 @@
     }
 
     @Override
-    public void onStackTaskRemoved(TaskStack stack, Task t) {
+    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
         // Update the task offsets
         mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
 
         // Remove the view associated with this task, we can't rely on updateTransforms
         // to work here because the task is no longer in the list
-        TaskView tv = getChildViewForTask(t);
+        TaskView tv = getChildViewForTask(removedTask);
         if (tv != null) {
             mViewPool.returnViewToPool(tv);
         }
 
         // Notify the callback that we've removed the task and it can clean up after it
-        mCb.onTaskViewDismissed(t);
+        mCb.onTaskViewDismissed(removedTask);
 
         // Update the min/max scroll and animate other task views into their new positions
         updateMinMaxScroll(true);
         int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
         requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
 
+        // Update the new front most task
+        if (newFrontMostTask != null) {
+            TaskView frontTv = getChildViewForTask(newFrontMostTask);
+            if (frontTv != null) {
+                frontTv.onTaskBound(newFrontMostTask);
+            }
+        }
+
         // If there are no remaining tasks, then either unfilter the current stack, or just close
         // the activity if there are no filtered stacks
         if (mStack.getTaskCount() == 0) {
@@ -1072,7 +1091,7 @@
             addView(tv, insertIndex);
 
             // Set the callbacks and listeners for this new view
-            setTouchOnTaskView(tv, true);
+            tv.setTouchEnabled(true);
             tv.setCallbacks(this);
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -1115,18 +1134,7 @@
     }
 
     @Override
-    public void onTaskViewDismissed(TaskView tv) {
-        Task task = tv.getTask();
-        // Remove the task from the view
-        mStack.removeTask(task);
-    }
-
-    /**** View.OnClickListener Implementation ****/
-
-    @Override
-    public void onClick(View v) {
-        TaskView tv = (TaskView) v;
-        Task task = tv.getTask();
+    public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
         if (Console.Enabled) {
             Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
                     task + " cb: " + mCb);
@@ -1136,10 +1144,17 @@
         mUIDozeTrigger.stopDozing();
 
         if (mCb != null) {
-            mCb.onTaskViewClicked(this, tv, mStack, task);
+            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
         }
     }
 
+    @Override
+    public void onTaskViewDismissed(TaskView tv) {
+        Task task = tv.getTask();
+        // Remove the task from the view
+        mStack.removeTask(task);
+    }
+
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
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 789b4f7..908e063 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -95,9 +95,11 @@
 
         if (numTasks <= 1) {
             // If there is only one task, then center the task in the stack rect (sans peek)
-            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
+            mMinScroll = mMaxScroll = -(stackHeight -
+                    (taskHeight + mConfig.taskViewLockToAppButtonHeight)) / 2;
         } else {
-            int maxScrollHeight = taskHeight + getStackScrollForTaskIndex(tasks.get(tasks.size() - 1));
+            int maxScrollHeight = getStackScrollForTaskIndex(tasks.get(tasks.size() - 1))
+                    + taskHeight + mConfig.taskViewLockToAppButtonHeight;
             mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
             mMaxScroll = maxScrollHeight - stackHeight;
         }
@@ -133,7 +135,7 @@
         // Set the y translation
         if (boundedT < 0f) {
             transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
-                    numPeekCards) * peekHeight - scaleYOffset);
+                    numPeekCards) * peekHeight - scaleYOffset - scaleBarYOffset);
         } else {
             transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset - scaleBarYOffset);
         }
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 e186e2e..15ace13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -377,7 +377,9 @@
         // Enable HW layers on that task
         tv.enableHwLayers();
         // Disallow touch events from this task view
-        mSv.setTouchOnTaskView(tv, false);
+        tv.setTouchEnabled(false);
+        // Hide the footer
+        tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
         // Disallow parents from intercepting touch events
         final ViewParent parent = mSv.getParent();
         if (parent != null) {
@@ -413,7 +415,9 @@
         // Re-enable clipping with the stack
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
-        mSv.setTouchOnTaskView(tv, true);
+        tv.setTouchEnabled(true);
+        // Restore the footer
+        tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
     }
 
     @Override
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 1ba0560..5524e15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -28,6 +27,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewOutlineProvider;
 import android.view.ViewPropertyAnimator;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
@@ -36,6 +36,7 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
 
 
 /* A task view */
@@ -45,11 +46,16 @@
     interface TaskViewCallbacks {
         public void onTaskViewAppIconClicked(TaskView tv);
         public void onTaskViewAppInfoClicked(TaskView tv);
+        public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
         public void onTaskViewDismissed(TaskView tv);
     }
 
     RecentsConfiguration mConfig;
 
+    int mFooterHeight;
+    int mMaxFooterHeight;
+    ObjectAnimator mFooterAnimator;
+
     int mDim;
     int mMaxDim;
     AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -64,6 +70,7 @@
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
+    View mLockToAppButtonView;
     TaskViewCallbacks mCb;
 
     // Optimizations
@@ -103,9 +110,20 @@
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
+        mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
         setWillNotDraw(false);
         setClipToOutline(true);
         setDim(getDim());
+        setFooterHeight(getFooterHeight());
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public boolean getOutline(View view, Outline outline) {
+                int height = getHeight() - mMaxFooterHeight + mFooterHeight;
+                outline.setRoundRect(0, 0, getWidth(), height,
+                        mConfig.taskViewRoundedCornerRadiusPx);
+                return true;
+            }
+        });
     }
 
     @Override
@@ -118,6 +136,7 @@
         // Bind the views
         mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
         mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+        mLockToAppButtonView = findViewById(R.id.lock_to_app);
 
         if (mTaskDataLoaded) {
             onTaskDataLoaded();
@@ -126,13 +145,19 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        // Update the outline
-        Outline o = new Outline();
-        o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
-                mConfig.taskViewRoundedCornerRadiusPx);
-        setOutline(o);
+        // Measure the bar view, thumbnail, and lock-to-app buttons
+        mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
+        mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
+                        MeasureSpec.EXACTLY));
+        // Measure the thumbnail height to be the same as the width
+        mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
+        setMeasuredDimension(width, height);
     }
 
     /** Set callback */
@@ -242,10 +267,10 @@
 
     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
      * first layout because the actual animation into recents may take a long time. */
-    public void prepareEnterRecentsAnimation(boolean isTaskViewFrontMost, int offsetY, int offscreenY,
-                                             Rect taskRect) {
+    public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offsetY,
+                                             int offscreenY) {
         if (mConfig.launchedFromAppWithScreenshot) {
-            if (isTaskViewFrontMost) {
+            if (isTaskViewLaunchTargetTask) {
                 // Hide the task view as we are going to animate the full screenshot into view
                 // and then replace it with this view once we are done
                 setVisibility(View.INVISIBLE);
@@ -259,7 +284,7 @@
             }
 
         } else if (mConfig.launchedFromAppWithThumbnail) {
-            if (isTaskViewFrontMost) {
+            if (isTaskViewLaunchTargetTask) {
                 // Hide the front most task bar view so we can animate it in
                 mBarView.prepareEnterRecentsAnimation();
                 // Set the dim to 0 so we can animate it in
@@ -282,7 +307,7 @@
         TaskViewTransform transform = ctx.currentTaskTransform;
 
         if (mConfig.launchedFromAppWithScreenshot) {
-            if (ctx.isCurrentTaskFrontMost) {
+            if (ctx.isCurrentTaskLaunchTarget) {
                 // Animate the full screenshot down first, before swapping with this task view
                 ctx.fullScreenshotView.animateOnEnterRecents(ctx, new Runnable() {
                     @Override
@@ -290,6 +315,8 @@
                         // Animate the task bar of the first task view
                         mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
                         setVisibility(View.VISIBLE);
+                        // Animate the footer into view
+                        animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0);
                         // Decrement the post animation trigger
                         ctx.postAnimationTrigger.decrement();
                     }
@@ -318,7 +345,7 @@
             ctx.postAnimationTrigger.increment();
 
         } else if (mConfig.launchedFromAppWithThumbnail) {
-            if (ctx.isCurrentTaskFrontMost) {
+            if (ctx.isCurrentTaskLaunchTarget) {
                 // Animate the task bar of the first task view
                 mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip);
 
@@ -336,6 +363,10 @@
                 });
                 anim.start();
                 ctx.postAnimationTrigger.increment();
+
+                // Animate the footer into view
+                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration,
+                        mConfig.taskBarEnterAnimDelay);
             } else {
                 mEnableThumbnailClip.run();
             }
@@ -367,9 +398,16 @@
                     })
                     .start();
             ctx.postAnimationTrigger.increment();
+
+            // Animate the footer into view
+            animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration,
+                    mConfig.taskBarEnterAnimDelay);
         } else {
             // Otherwise, just enable the thumbnail clip
             mEnableThumbnailClip.run();
+
+            // Animate the footer into view
+            animateFooterVisibility(true, 0, 0);
         }
     }
 
@@ -458,12 +496,14 @@
     void enableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
         mBarView.enableHwLayers();
+        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
     }
 
     /** Disable the hw layers on this task view */
     void disableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
         mBarView.disableHwLayers();
+        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
     }
 
     /** Sets the stubbed state of this task view. */
@@ -500,6 +540,57 @@
         }
     }
 
+    /** Sets the footer height. */
+    public void setFooterHeight(int height) {
+        mFooterHeight = height;
+        invalidateOutline();
+        invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
+                getMeasuredHeight());
+    }
+
+    /** Gets the footer height. */
+    public int getFooterHeight() {
+        return mFooterHeight;
+    }
+
+    /** Animates the footer into and out of view. */
+    public void animateFooterVisibility(boolean visible, int duration, int delay) {
+        if (!mTask.canLockToTask) return;
+        if (mMaxFooterHeight <= 0) return;
+
+        if (mFooterAnimator != null) {
+            mFooterAnimator.removeAllListeners();
+            mFooterAnimator.cancel();
+        }
+        int height = visible ? mMaxFooterHeight : 0;
+        if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
+            if (duration > 0) {
+                setFooterHeight(0);
+            } else {
+                setFooterHeight(mMaxFooterHeight);
+            }
+            mLockToAppButtonView.setVisibility(View.VISIBLE);
+        }
+        if (duration > 0) {
+            mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
+            mFooterAnimator.setDuration(duration);
+            mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+            if (!visible) {
+                mFooterAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mLockToAppButtonView.setVisibility(View.INVISIBLE);
+                    }
+                });
+            }
+            mFooterAnimator.start();
+        } else {
+            if (!visible) {
+                mLockToAppButtonView.setVisibility(View.INVISIBLE);
+            }
+        }
+    }
+
     /** Returns the current dim. */
     public void setDim(int dim) {
         mDim = dim;
@@ -585,6 +676,11 @@
     public void onTaskBound(Task t) {
         mTask = t;
         mTask.setCallbacks(this);
+        if (getMeasuredWidth() == 0) {
+            animateFooterVisibility(t.canLockToTask, 0, 0);
+        } else {
+            animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
+        }
     }
 
     @Override
@@ -598,6 +694,7 @@
                 mBarView.mApplicationIcon.setOnClickListener(this);
             }
             mBarView.mDismissButton.setOnClickListener(this);
+            mLockToAppButtonView.setOnClickListener(this);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 if (mConfig.developerOptionsEnabled) {
                     mBarView.mApplicationIcon.setOnLongClickListener(this);
@@ -619,6 +716,7 @@
                 mBarView.mApplicationIcon.setOnClickListener(null);
             }
             mBarView.mDismissButton.setOnClickListener(null);
+            mLockToAppButtonView.setOnClickListener(null);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 mBarView.mApplicationIcon.setOnLongClickListener(null);
             }
@@ -626,6 +724,11 @@
         mTaskDataLoaded = false;
     }
 
+    /** Enables/disables handling touch on this task view. */
+    void setTouchEnabled(boolean enabled) {
+        setOnClickListener(enabled ? this : null);
+    }
+
     @Override
     public void onClick(final View v) {
         // We purposely post the handler delayed to allow for the touch feedback to draw
@@ -643,6 +746,10 @@
                             mCb.onTaskViewDismissed(tv);
                         }
                     });
+                    // Hide the footer
+                    tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0);
+                } else if (v == tv || v == mLockToAppButtonView) {
+                    mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
                 }
             }
         }, 125);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
index a531583..e50a5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -36,7 +36,7 @@
         // The transform of the current task view
         TaskViewTransform currentTaskTransform;
         // Whether this is the front most task view
-        boolean isCurrentTaskFrontMost;
+        boolean isCurrentTaskLaunchTarget;
         // The view index of the current task view
         int currentStackViewIndex;
         // The total number of task views
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 843db04..d9719ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,39 +18,50 @@
 
 import android.content.Context;
 import android.graphics.Outline;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
 
 /**
  * Like {@link ExpandableView}, but setting an outline for the height and clipping.
  */
 public abstract class ExpandableOutlineView extends ExpandableView {
 
-    private final Outline mOutline = new Outline();
+    private final Rect mOutlineRect = new Rect();
     private boolean mCustomOutline;
     private float mDensity;
 
     public ExpandableOutlineView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mDensity = getResources().getDisplayMetrics().density;
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public boolean getOutline(View view, Outline outline) {
+                if (!mCustomOutline) {
+                    outline.setRect(0,
+                            mClipTopAmount,
+                            getWidth(),
+                            Math.max(mActualHeight, mClipTopAmount));
+                } else {
+                    outline.setRect(mOutlineRect);
+                }
+                return true;
+            }
+        });
     }
 
     @Override
     public void setActualHeight(int actualHeight, boolean notifyListeners) {
         super.setActualHeight(actualHeight, notifyListeners);
-        updateOutline();
+        invalidateOutline();
     }
 
     @Override
     public void setClipTopAmount(int clipTopAmount) {
         super.setClipTopAmount(clipTopAmount);
-        updateOutline();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        updateOutline();
+        invalidateOutline();
     }
 
     protected void setOutlineRect(RectF rect) {
@@ -58,32 +69,20 @@
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
         } else {
             mCustomOutline = false;
-            updateOutline();
+            invalidateOutline();
         }
     }
 
     protected void setOutlineRect(float left, float top, float right, float bottom) {
         mCustomOutline = true;
 
-        int rectLeft = (int) left;
-        int rectTop = (int) top;
-        int rectRight = (int) right;
-        int rectBottom = (int) bottom;
+        mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
 
         // Outlines need to be at least 1 dp
-        rectBottom = (int) Math.max(top + mDensity, rectBottom);
-        rectRight = (int) Math.max(left + mDensity, rectRight);
-        mOutline.setRect(rectLeft, rectTop, rectRight, rectBottom);
-        setOutline(mOutline);
+        mOutlineRect.bottom = (int) Math.max(top + mDensity, mOutlineRect.bottom);
+        mOutlineRect.right = (int) Math.max(left + mDensity, mOutlineRect.right);
+
+        invalidateOutline();
     }
 
-    private void updateOutline() {
-        if (!mCustomOutline) {
-            mOutline.setRect(0,
-                    mClipTopAmount,
-                    getWidth(),
-                    Math.max(mActualHeight, mClipTopAmount));
-            setOutline(mOutline);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
index 650abaa..dfeadc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -61,7 +61,7 @@
         super.onLayout(changed, left, top, right, bottom);
         mLine.setPivotX(mLine.getWidth() / 2);
         mLine.setPivotY(mLine.getHeight() / 2);
-        setOutline(null);
+        setOutlineProvider(null);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index c962dde..7fb5693 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -639,7 +639,6 @@
     }
 
     public void collapse() {
-        // TODO: abort animation or ongoing touch
         if (DEBUG) logf("collapse: " + this);
         if (mPeekPending || mPeekAnimator != null) {
             mCollapseAfterPeek = true;
@@ -650,7 +649,7 @@
                 removeCallbacks(mPeekRunnable);
                 mPeekRunnable.run();
             }
-        } else if (!isFullyCollapsed()) {
+        } else if (!isFullyCollapsed() && !mTracking) {
             if (mHeightAnimator != null) {
                 mHeightAnimator.cancel();
             }
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 04f9c72..9d7d933 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2316,7 +2316,12 @@
             mGestureRec.dump(fd, pw, args);
         }
 
-        mNetworkController.dump(fd, pw, args);
+        if (mNetworkController != null) {
+            mNetworkController.dump(fd, pw, args);
+        }
+        if (mBluetoothController != null) {
+            mBluetoothController.dump(fd, pw, args);
+        }
     }
 
     private String hunStateToString(Entry entry) {
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 0fbdeeb4..33d1b15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -24,6 +24,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
@@ -51,7 +52,8 @@
     private ViewGroup mSystemIconsContainer;
     private View mSystemIconsSuperContainer;
     private View mDateTime;
-    private TextView mClock;
+    private View mTime;
+    private View mAmPm;
     private View mKeyguardCarrierText;
     private MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
@@ -61,6 +63,8 @@
     private View mSignalCluster;
     private View mSettingsButton;
     private View mQsDetailHeader;
+    private TextView mQsDetailHeaderTitle;
+    private Switch mQsDetailHeaderSwitch;
     private View mEmergencyCallsOnly;
     private TextView mBatteryLevel;
 
@@ -95,7 +99,6 @@
     private QSPanel mQSPanel;
 
     private final Rect mClipBounds = new Rect();
-    private final Outline mOutline = new Outline();
 
     public StatusBarHeaderView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -108,7 +111,8 @@
         mSystemIconsContainer = (ViewGroup) findViewById(R.id.system_icons_container);
         mSystemIconsSuperContainer.setOnClickListener(this);
         mDateTime = findViewById(R.id.datetime);
-        mClock = (TextView) findViewById(R.id.clock);
+        mTime = findViewById(R.id.time_view);
+        mAmPm = findViewById(R.id.am_pm_view);
         mKeyguardCarrierText = findViewById(R.id.keyguard_carrier_text);
         mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar);
@@ -118,6 +122,8 @@
         mSettingsButton.setOnClickListener(this);
         mQsDetailHeader = findViewById(R.id.qs_detail_header);
         mQsDetailHeader.setAlpha(0);
+        mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
+        mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
         mEmergencyCallsOnly = findViewById(R.id.header_emergency_calls_only);
         mBatteryLevel = (TextView) findViewById(R.id.battery_level);
         loadDimens();
@@ -132,8 +138,19 @@
                     // width changed, update clipping
                     setClipping(getHeight());
                 }
-                mClock.setPivotX(0);
-                mClock.setPivotY(mClock.getBaseline());
+                boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+                mTime.setPivotX(rtl ? mTime.getWidth() : 0);
+                mTime.setPivotY(mTime.getBaseline());
+                mAmPm.setPivotX(rtl ? mAmPm.getWidth() : 0);
+                mAmPm.setPivotY(mAmPm.getBaseline());
+                updateAmPmTranslation();
+            }
+        });
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public boolean getOutline(View view, Outline outline) {
+                outline.setRect(mClipBounds);
+                return true;
             }
         });
     }
@@ -164,8 +181,8 @@
                 getResources().getDimensionPixelSize(R.dimen.multi_user_avatar_collapsed_size)
                 / (float) mMultiUserAvatar.getLayoutParams().width;
         mClockCollapsedScaleFactor =
-                getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size)
-                / mClock.getTextSize();
+                (float) getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size)
+                / (float) getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
         mBatteryPaddingEnd =
                 getResources().getDimensionPixelSize(R.dimen.battery_level_padding_end);
     }
@@ -315,13 +332,21 @@
     }
 
     private void updateClockScale() {
+        mAmPm.setScaleX(mClockCollapsedScaleFactor);
+        mAmPm.setScaleY(mClockCollapsedScaleFactor);
         if (!mExpanded || mOverscrolled) {
-            mClock.setScaleX(mClockCollapsedScaleFactor);
-            mClock.setScaleY(mClockCollapsedScaleFactor);
+            mTime.setScaleX(mClockCollapsedScaleFactor);
+            mTime.setScaleY(mClockCollapsedScaleFactor);
         } else {
-            mClock.setScaleX(1f);
-            mClock.setScaleY(1f);
+            mTime.setScaleX(1f);
+            mTime.setScaleY(1f);
         }
+        updateAmPmTranslation();
+    }
+
+    private void updateAmPmTranslation() {
+        boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        mAmPm.setTranslationX((rtl ? 1 : -1) * mTime.getWidth() * (1 - mTime.getScaleX()));
     }
 
     private void updateBatteryLevelPaddingEnd() {
@@ -409,8 +434,7 @@
     private void setClipping(float height) {
         mClipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), (int) height);
         setClipBounds(mClipBounds);
-        mOutline.setRect(mClipBounds);
-        setOutline(mOutline);
+        invalidateOutline();
     }
 
     public void attachSystemIcons(LinearLayout systemIcons) {
@@ -530,10 +554,22 @@
             });
         }
 
+        @Override
+        public void onScanStateChanged(final boolean state) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    handleScanStateChanged(state);
+                }
+            });
+        }
+
         private void handleToggleStateChanged(boolean state) {
-            final Switch headerSwitch = (Switch)
-                    mQsDetailHeader.findViewById(android.R.id.toggle);
-            headerSwitch.setChecked(state);
+            mQsDetailHeaderSwitch.setChecked(state);
+        }
+
+        private void handleScanStateChanged(boolean state) {
+            // TODO - waiting on framework asset
         }
 
         private void handleShowingDetail(final QSTile.DetailAdapter detail) {
@@ -541,18 +577,14 @@
             transition(mDateTime, !showingDetail);
             transition(mQsDetailHeader, showingDetail);
             if (showingDetail) {
-                final TextView headerTitle = (TextView)
-                        mQsDetailHeader.findViewById(android.R.id.title);
-                headerTitle.setText(detail.getTitle());
-                final Switch headerSwitch = (Switch)
-                        mQsDetailHeader.findViewById(android.R.id.toggle);
+                mQsDetailHeaderTitle.setText(detail.getTitle());
                 final Boolean toggleState = detail.getToggleState();
                 if (toggleState == null) {
-                    headerSwitch.setVisibility(INVISIBLE);
+                    mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
                     mQsDetailHeader.setClickable(false);
                 } else {
-                    headerSwitch.setVisibility(VISIBLE);
-                    headerSwitch.setChecked(toggleState);
+                    mQsDetailHeaderSwitch.setVisibility(VISIBLE);
+                    mQsDetailHeaderSwitch.setChecked(toggleState);
                     mQsDetailHeader.setClickable(true);
                     mQsDetailHeader.setOnClickListener(new OnClickListener() {
                         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 8e9fb30..cbdd138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import java.util.Set;
+
 public interface BluetoothController {
     void addStateChangedCallback(Callback callback);
     void removeStateChangedCallback(Callback callback);
@@ -26,8 +28,32 @@
     boolean isBluetoothConnecting();
     String getLastDeviceName();
     void setBluetoothEnabled(boolean enabled);
+    Set<PairedDevice> getPairedDevices();
+    void connect(PairedDevice device);
+    void disconnect(PairedDevice device);
 
     public interface Callback {
         void onBluetoothStateChange(boolean enabled, boolean connecting);
+        void onBluetoothPairedDevicesChanged();
+    }
+
+    public static final class PairedDevice {
+        public static int STATE_DISCONNECTED = 0;
+        public static int STATE_CONNECTING = 1;
+        public static int STATE_CONNECTED = 2;
+        public static int STATE_DISCONNECTING = 3;
+
+        public String id;
+        public String name;
+        public int state = STATE_DISCONNECTED;
+        public Object tag;
+
+        public static String stateToString(int state) {
+            if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+            if (state == STATE_CONNECTING) return "STATE_CONNECTING";
+            if (state == STATE_CONNECTED) return "STATE_CONNECTED";
+            if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+            return "UNKNOWN";
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 379b509..f021623 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,49 +16,91 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.bluetooth.BluetoothAdapter.ERROR;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.profileStateToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.ParcelUuid;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
 
+import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.Set;
 
-public class BluetoothControllerImpl extends BroadcastReceiver implements BluetoothController {
-    private static final String TAG = "StatusBar.BluetoothController";
+public class BluetoothControllerImpl implements BluetoothController {
+    private static final String TAG = "BluetoothController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private final Context mContext;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-    private final Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>();
     private final BluetoothAdapter mAdapter;
+    private final Receiver mReceiver = new Receiver();
+    private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
 
     private boolean mEnabled;
     private boolean mConnecting;
     private BluetoothDevice mLastDevice;
 
     public BluetoothControllerImpl(Context context) {
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
-        context.registerReceiver(this, filter);
-
-        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            handleAdapterStateChange(adapter.getState());
+        mContext = context;
+        final BluetoothManager bluetoothManager =
+                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+        mAdapter = bluetoothManager.getAdapter();
+        if (mAdapter == null) {
+            Log.w(TAG, "Default BT adapter not found");
+            return;
         }
-        fireCallbacks();
+
+        mReceiver.register();
+        setAdapterState(mAdapter.getState());
         updateBondedBluetoothDevices();
     }
 
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("BluetoothController state:");
+        pw.print("  mAdapter="); pw.println(mAdapter);
+        pw.print("  mEnabled="); pw.println(mEnabled);
+        pw.print("  mConnecting="); pw.println(mConnecting);
+        pw.print("  mLastDevice="); pw.println(mLastDevice);
+        pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
+        pw.print("  mDeviceInfo.size="); pw.println(mDeviceInfo.size());
+        for (int i = 0; i < mDeviceInfo.size(); i++) {
+            final BluetoothDevice device = mDeviceInfo.keyAt(i);
+            final DeviceInfo info = mDeviceInfo.valueAt(i);
+            pw.print("    "); pw.print(deviceToString(device));
+            pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
+            pw.print("    "); pw.println(infoToString(info));
+        }
+    }
+
+    private static String infoToString(DeviceInfo info) {
+        return info == null ? null : ("connectionState=" +
+                connectionStateToString(info.connectionState) + ",bonded=" + info.bonded);
+    }
+
     public void addStateChangedCallback(Callback cb) {
         mCallbacks.add(cb);
-        fireCallback(cb);
+        fireStateChange(cb);
     }
 
     @Override
@@ -99,64 +141,191 @@
         return mAdapter != null;
     }
 
-    public Set<BluetoothDevice> getBondedBluetoothDevices() {
-        return mBondedDevices;
+    @Override
+    public ArraySet<PairedDevice> getPairedDevices() {
+        final ArraySet<PairedDevice> rt = new ArraySet<>();
+        for (int i = 0; i < mDeviceInfo.size(); i++) {
+            final BluetoothDevice device = mDeviceInfo.keyAt(i);
+            final DeviceInfo info = mDeviceInfo.valueAt(i);
+            if (!info.bonded) continue;
+            final PairedDevice paired = new PairedDevice();
+            paired.id = device.getAddress();
+            paired.tag = device;
+            paired.name = device.getAliasName();
+            paired.state = connectionStateToPairedDeviceState(info.connectionState);
+            rt.add(paired);
+        }
+        return rt;
+    }
+
+    private static int connectionStateToPairedDeviceState(int state) {
+        if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
+        if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
+        if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
+        return PairedDevice.STATE_DISCONNECTED;
+    }
+
+    @Override
+    public void connect(final PairedDevice pd) {
+        connect(pd, true);
+    }
+
+    @Override
+    public void disconnect(PairedDevice pd) {
+        connect(pd, false);
+    }
+
+    private void connect(PairedDevice pd, final boolean connect) {
+        if (mAdapter == null || pd == null || pd.tag == null) return;
+        final BluetoothDevice device = (BluetoothDevice) pd.tag;
+        final String action = connect ? "connect" : "disconnect";
+        if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
+        final SparseBooleanArray profiles = new SparseBooleanArray();
+        for (ParcelUuid uuid : device.getUuids()) {
+            final int profile = uuidToProfile(uuid);
+            if (profile == 0) {
+                Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
+                        + uuidToString(uuid));
+                continue;
+            }
+            final int profileState = mAdapter.getProfileConnectionState(profile);
+            if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile)
+                    + " state = " + profileStateToString(profileState));
+            final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED;
+            if (connect != connected) {
+                profiles.put(profile, true);
+            }
+        }
+        for (int i = 0; i < profiles.size(); i++) {
+            final int profile = profiles.keyAt(i);
+            mAdapter.getProfileProxy(mContext, new ServiceListener() {
+                @Override
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile));
+                    final Profile p = BluetoothUtil.getProfile(proxy);
+                    if (p == null) {
+                        Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
+                    } else {
+                        final boolean ok = connect ? p.connect(device) : p.disconnect(device);
+                        if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
+                                + (ok ? "succeeded" : "failed"));
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(int profile) {
+                    if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
+                }
+            }, profile);
+        }
     }
 
     @Override
     public String getLastDeviceName() {
-        return mLastDevice != null ? mLastDevice.getAliasName()
-                : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getAliasName()
-                : null;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-
-        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-            handleAdapterStateChange(
-                    intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
-        }
-        if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
-            mConnecting = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)
-                    == BluetoothAdapter.STATE_CONNECTING;
-            mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        }
-        if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
-            mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        }
-        fireCallbacks();
-        updateBondedBluetoothDevices();
+        return mLastDevice != null ? mLastDevice.getAliasName() : null;
     }
 
     private void updateBondedBluetoothDevices() {
-        mBondedDevices.clear();
-
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            Set<BluetoothDevice> devices = adapter.getBondedDevices();
-            if (devices != null) {
-                for (BluetoothDevice device : devices) {
-                    if (device.getBondState() != BluetoothDevice.BOND_NONE) {
-                        mBondedDevices.add(device);
-                    }
+        if (mAdapter == null) return;
+        final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        for (DeviceInfo info : mDeviceInfo.values()) {
+            info.bonded = false;
+        }
+        int bondedCount = 0;
+        BluetoothDevice lastBonded = null;
+        if (bondedDevices != null) {
+            for (BluetoothDevice bondedDevice : bondedDevices) {
+                final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
+                updateInfo(bondedDevice).bonded = bonded;
+                if (bonded) {
+                    bondedCount++;
+                    lastBonded = bondedDevice;
                 }
             }
         }
+        if (mLastDevice == null && bondedCount == 1) {
+            mLastDevice = lastBonded;
+        }
+        firePairedDevicesChanged();
     }
 
-    private void handleAdapterStateChange(int adapterState) {
-        mEnabled = (adapterState == BluetoothAdapter.STATE_ON);
-    }
-
-    private void fireCallbacks() {
+    private void firePairedDevicesChanged() {
         for (Callback cb : mCallbacks) {
-            fireCallback(cb);
+            cb.onBluetoothPairedDevicesChanged();
         }
     }
 
-    private void fireCallback(Callback cb) {
+    private void setAdapterState(int adapterState) {
+        final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
+        if (mEnabled == enabled) return;
+        mEnabled = enabled;
+        fireStateChange();
+    }
+
+    private void setConnecting(boolean connecting) {
+        if (mConnecting == connecting) return;
+        mConnecting = connecting;
+        fireStateChange();
+    }
+
+    private void fireStateChange() {
+        for (Callback cb : mCallbacks) {
+            fireStateChange(cb);
+        }
+    }
+
+    private void fireStateChange(Callback cb) {
         cb.onBluetoothStateChange(mEnabled, mConnecting);
     }
+
+    private final class Receiver extends BroadcastReceiver {
+        public void register() {
+            final IntentFilter filter = new IntentFilter();
+            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+            filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+            filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
+            mContext.registerReceiver(this, filter);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+                setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
+                if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
+            } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+                final DeviceInfo info = updateInfo(device);
+                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                        ERROR);
+                if (state != ERROR) {
+                    info.connectionState = state;
+                }
+                mLastDevice = device;
+                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
+                        + connectionStateToString(state) + " " + deviceToString(device));
+                setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING);
+            } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
+                updateInfo(device);
+                mLastDevice = device;
+            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
+                if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
+                // we'll update all bonded devices below
+            }
+            updateBondedBluetoothDevices();
+        }
+    }
+
+    private DeviceInfo updateInfo(BluetoothDevice device) {
+        DeviceInfo info = mDeviceInfo.get(device);
+        info = info != null ? info : new DeviceInfo();
+        mDeviceInfo.put(device, info);
+        return info;
+    }
+
+    private static class DeviceInfo {
+        int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
+        boolean bonded;  // per getBondedDevices
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
new file mode 100644
index 0000000..1b4be85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.text.TextUtils;
+
+public class BluetoothUtil {
+
+    public static String profileToString(int profile) {
+        if (profile == BluetoothProfile.HEADSET) return "HEADSET";
+        if (profile == BluetoothProfile.A2DP) return "A2DP";
+        if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
+        return "UNKNOWN";
+    }
+
+    public static String profileStateToString(int state) {
+        if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED";
+        if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING";
+        if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+        if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+        return "STATE_UNKNOWN";
+    }
+
+    public static String uuidToString(ParcelUuid uuid) {
+        if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink";
+        if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource";
+        if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist";
+        if (BluetoothUuid.HSP.equals(uuid)) return "HSP";
+        if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG";
+        if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree";
+        if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG";
+        if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController";
+        if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget";
+        if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush";
+        if (BluetoothUuid.Hid.equals(uuid)) return "Hid";
+        if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp";
+        if (BluetoothUuid.PANU.equals(uuid)) return "PANU";
+        if (BluetoothUuid.NAP.equals(uuid)) return "NAP";
+        if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP";
+        if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE";
+        if (BluetoothUuid.MAP.equals(uuid)) return "MAP";
+        if (BluetoothUuid.MNS.equals(uuid)) return "MNS";
+        if (BluetoothUuid.MAS.equals(uuid)) return "MAS";
+        return uuid != null ? uuid.toString() : null;
+    }
+
+    public static String connectionStateToString(int connectionState) {
+        if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+        if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED";
+        if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+        if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING";
+        return "ERROR";
+    }
+
+    public static String deviceToString(BluetoothDevice device) {
+        return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']');
+    }
+
+    public static String uuidsToString(BluetoothDevice device) {
+        if (device == null) return null;
+        final ParcelUuid[] ids = device.getUuids();
+        if (ids == null) return null;
+        final String[] tokens = new String[ids.length];
+        for (int i = 0; i < tokens.length; i++) {
+            tokens[i] = uuidToString(ids[i]);
+        }
+        return TextUtils.join(",", tokens);
+    }
+
+    public static int uuidToProfile(ParcelUuid uuid) {
+        if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP;
+        if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP;
+
+        if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET;
+        if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET;
+
+        if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP;
+        if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP;
+        if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP;
+
+        if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;
+
+        return 0;
+    }
+
+    public static Profile getProfile(BluetoothProfile p) {
+        if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p);
+        if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p);
+        if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p);
+        if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p);
+        if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p);
+        if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p);
+        if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p);
+        return null;
+    }
+
+    private static Profile newProfile(final BluetoothA2dp a2dp) {
+        return new Profile() {
+            @Override
+            public boolean connect(BluetoothDevice device) {
+                return a2dp.connect(device);
+            }
+
+            @Override
+            public boolean disconnect(BluetoothDevice device) {
+                return a2dp.disconnect(device);
+            }
+        };
+    }
+
+    private static Profile newProfile(final BluetoothHeadset headset) {
+        return new Profile() {
+            @Override
+            public boolean connect(BluetoothDevice device) {
+                return headset.connect(device);
+            }
+
+            @Override
+            public boolean disconnect(BluetoothDevice device) {
+                return headset.disconnect(device);
+            }
+        };
+    }
+
+    private static Profile newProfile(final BluetoothA2dpSink sink) {
+        return new Profile() {
+            @Override
+            public boolean connect(BluetoothDevice device) {
+                return sink.connect(device);
+            }
+
+            @Override
+            public boolean disconnect(BluetoothDevice device) {
+                return sink.disconnect(device);
+            }
+        };
+    }
+
+    private static Profile newProfile(final BluetoothHeadsetClient client) {
+        return new Profile() {
+            @Override
+            public boolean connect(BluetoothDevice device) {
+                return client.connect(device);
+            }
+
+            @Override
+            public boolean disconnect(BluetoothDevice device) {
+                return client.disconnect(device);
+            }
+        };
+    }
+
+    private static Profile newProfile(final BluetoothInputDevice input) {
+        return new Profile() {
+            @Override
+            public boolean connect(BluetoothDevice device) {
+                return input.connect(device);
+            }
+
+            @Override
+            public boolean disconnect(BluetoothDevice device) {
+                return input.disconnect(device);
+            }
+        };
+    }
+
+    private static Profile newProfile(final BluetoothMap map) {
+        return new Profile() {
+            @Override
+            public boolean connect(BluetoothDevice device) {
+                return map.connect(device);
+            }
+
+            @Override
+            public boolean disconnect(BluetoothDevice device) {
+                return map.disconnect(device);
+            }
+        };
+    }
+
+    private static Profile newProfile(final BluetoothPan pan) {
+        return new Profile() {
+            @Override
+            public boolean connect(BluetoothDevice device) {
+                return pan.connect(device);
+            }
+
+            @Override
+            public boolean disconnect(BluetoothDevice device) {
+                return pan.disconnect(device);
+            }
+        };
+    }
+
+    // common abstraction for supported profiles
+    public interface Profile {
+        boolean connect(BluetoothDevice device);
+        boolean disconnect(BluetoothDevice device);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 0a48e34..d232bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
@@ -159,6 +160,21 @@
 
     // ViewGroup methods
 
+    private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER =
+            new ViewOutlineProvider() {
+        @Override
+        public boolean getOutline(View view, Outline outline) {
+            int outlineLeft = view.getPaddingLeft();
+            int outlineTop = view.getPaddingTop();
+
+            // Apply padding to shadow.
+            outline.setRect(outlineLeft, outlineTop,
+                    view.getWidth() - outlineLeft - view.getPaddingRight(),
+                    view.getHeight() - outlineTop - view.getPaddingBottom());
+            return true;
+        }
+    };
+
     @Override
     public void onAttachedToWindow() {
         float densityScale = getResources().getDisplayMetrics().density;
@@ -174,6 +190,7 @@
         mExpandHelper = new ExpandHelper(getContext(), this, minHeight, maxHeight);
 
         mContentHolder = (ViewGroup) findViewById(R.id.content_holder);
+        mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER);
 
         if (mHeadsUp != null) {
             // whoops, we're on already!
@@ -232,20 +249,6 @@
         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
     }
 
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        Outline o = new Outline();
-
-        // Apply padding to shadow.
-        int outlineLeft = mContentHolder.getPaddingLeft();
-        int outlineTop = mContentHolder.getPaddingTop();
-        o.setRect(outlineLeft, outlineTop,
-                mContentHolder.getWidth() - outlineLeft - mContentHolder.getPaddingRight(),
-                mContentHolder.getHeight() - outlineTop - mContentHolder.getPaddingBottom());
-        mContentHolder.setOutline(o);
-    }
-
     // ExpandHelper.Callback methods
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
new file mode 100644
index 0000000..e7c4ede
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextClock;
+
+import com.android.systemui.R;
+
+/**
+ * Container for a clock which has two separate views for the clock itself and AM/PM indicator. This
+ * is used to scale the clock independently of AM/PM.
+ */
+public class SplitClockView extends LinearLayout {
+
+    private TextClock mTimeView;
+    private TextClock mAmPmView;
+
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_TIME_CHANGED.equals(action)
+                    || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+                    || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+                updatePatterns();
+            }
+        }
+    };
+
+    public SplitClockView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTimeView = (TextClock) findViewById(R.id.time_view);
+        mAmPmView = (TextClock) findViewById(R.id.am_pm_view);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        getContext().registerReceiver(mIntentReceiver, filter, null, null);
+
+        updatePatterns();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        getContext().unregisterReceiver(mIntentReceiver);
+    }
+
+    private void updatePatterns() {
+        String formatString = DateFormat.getTimeFormatString(getContext());
+        int index = getAmPmPartEndIndex(formatString);
+        String timeString;
+        String amPmString;
+        if (index == -1) {
+            timeString = formatString;
+            amPmString = "";
+        } else {
+            timeString = formatString.substring(0, index);
+            amPmString = formatString.substring(index);
+        }
+        mTimeView.setFormat12Hour(timeString);
+        mTimeView.setFormat24Hour(timeString);
+        mAmPmView.setFormat12Hour(amPmString);
+        mAmPmView.setFormat24Hour(amPmString);
+    }
+
+    /**
+     * @return the index where the AM/PM part starts at the end in {@code formatString} including
+     *         leading white spaces or {@code -1} if no AM/PM part is found or {@code formatString}
+     *         doesn't end with AM/PM part
+     */
+    private static int getAmPmPartEndIndex(String formatString) {
+        boolean hasAmPm = false;
+        int length = formatString.length();
+        for (int i = length - 1; i >= 0; i--) {
+            char c = formatString.charAt(i);
+            boolean isAmPm = c == 'a';
+            boolean isWhitespace = Character.isWhitespace(c);
+            if (isAmPm) {
+                hasAmPm = true;
+            }
+            if (isAmPm || isWhitespace) {
+                continue;
+            }
+            if (i == length - 1) {
+
+                // First character was not AM/PM and not whitespace, so it's not ending with AM/PM.
+                return -1;
+            } else {
+
+                // If we have AM/PM at all, return last index, or -1 to indicate that it's not
+                // ending with AM/PM.
+                return hasAmPm ? i + 1 : -1;
+            }
+        }
+
+        // Only AM/PM and whitespaces? The whole string is AM/PM. Else: Only whitespaces in the
+        // string.
+        return hasAmPm ? 0 : -1;
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 1721ec4..9bcffd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1317,7 +1317,8 @@
     }
 
     public int getPeekHeight() {
-        return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize;
+        return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
+                + mCollapseSecondCardPadding;
     }
 
     private int clampPadding(int desiredPadding) {
@@ -1795,11 +1796,11 @@
     }
 
     public int getEmptyBottomMargin() {
-        int emptyMargin = mMaxLayoutHeight - mContentHeight;
+        int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
         if (needsHeightAdaption()) {
-            emptyMargin = emptyMargin - mBottomStackSlowDownHeight - mBottomStackPeekSize;
+            emptyMargin -= mBottomStackSlowDownHeight;
         } else {
-            emptyMargin = emptyMargin - mBottomStackPeekSize;
+            emptyMargin -= mCollapseSecondCardPadding;
         }
         return Math.max(emptyMargin, 0);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 08216c4..ad2cf75 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -85,6 +85,7 @@
     private static final int FREE_DELAY = 10000;
     private static final int TIMEOUT_DELAY = 3000;
     private static final int TIMEOUT_DELAY_SHORT = 1500;
+    private static final int TIMEOUT_DELAY_COLLAPSED = 4500;
     private static final int TIMEOUT_DELAY_EXPANDED = 10000;
 
     private static final int MSG_VOLUME_CHANGED = 0;
@@ -263,7 +264,7 @@
             synchronized (sConfirmSafeVolumeLock) {
                 sConfirmSafeVolumeDialog = null;
             }
-            mVolumePanel.forceTimeout();
+            mVolumePanel.forceTimeout(0);
             mVolumePanel.updateStates();
         }
     }
@@ -293,7 +294,7 @@
                 public boolean onTouchEvent(MotionEvent event) {
                     if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
                             sConfirmSafeVolumeDialog == null) {
-                        forceTimeout();
+                        forceTimeout(0);
                         return true;
                     }
                     return false;
@@ -591,11 +592,18 @@
 
     private void updateTimeoutDelay() {
         mTimeoutDelay = mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT
-                : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED : TIMEOUT_DELAY;
+                : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED
+                : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED
+                : TIMEOUT_DELAY;
+    }
+
+    private boolean isZenPanelVisible() {
+        return mZenPanel != null && mZenPanel.getVisibility() == View.VISIBLE;
     }
 
     private void setZenPanelVisible(boolean visible) {
         if (LOGD) Log.d(mTag, "setZenPanelVisible " + visible + " mZenPanel=" + mZenPanel);
+        final boolean changing = visible != isZenPanelVisible();
         if (visible) {
             if (mZenPanel == null) {
                 mZenPanel = (ZenModePanel) mZenPanelStub.inflate();
@@ -629,6 +637,10 @@
                 mZenPanel.setVisibility(View.GONE);
             }
         }
+        if (changing) {
+            updateTimeoutDelay();
+            resetTimeout();
+        }
     }
 
     public void updateStates() {
@@ -718,8 +730,8 @@
         obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
     }
 
-    public void postDismiss() {
-        forceTimeout();
+    public void postDismiss(long delay) {
+        forceTimeout(delay);
     }
 
     public void postLayoutDirection(int layoutDirection) {
@@ -1198,16 +1210,17 @@
     }
 
     private void resetTimeout() {
-        if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis());
+        if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis()
+                + " delay=" + mTimeoutDelay);
         removeMessages(MSG_TIMEOUT);
         sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay);
         removeMessages(MSG_USER_ACTIVITY);
         sendEmptyMessage(MSG_USER_ACTIVITY);
     }
 
-    private void forceTimeout() {
+    private void forceTimeout(long delay) {
         removeMessages(MSG_TIMEOUT);
-        sendEmptyMessage(MSG_TIMEOUT);
+        sendEmptyMessageDelayed(MSG_TIMEOUT, delay);
     }
 
     public ZenModeController getZenController() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 51be833..e4f5870 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -11,12 +11,14 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -45,6 +47,7 @@
     private static final int DEFAULT = 1;  // enabled by default
 
     private final Handler mHandler = new Handler();
+
     private AudioManager mAudioManager;
     private MediaSessionManager mMediaSessionManager;
     private VolumeController mVolumeController;
@@ -52,6 +55,7 @@
 
     private VolumePanel mDialogPanel;
     private VolumePanel mPanel;
+    private int mDismissDelay;
 
     @Override
     public void start() {
@@ -79,6 +83,7 @@
     }
 
     private void initPanel() {
+        mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay);
         mPanel = new VolumePanel(mContext, null, new ZenModeControllerImpl(mContext, mHandler));
         mPanel.setCallback(new VolumePanel.Callback() {
             @Override
@@ -109,15 +114,20 @@
     private final Runnable mStartZenSettings = new Runnable() {
         @Override
         public void run() {
-            mDialogPanel.postDismiss();
-            try {
-                // Dismiss the lock screen when Settings starts.
-                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
-            } catch (RemoteException e) {
-            }
-            final Intent intent = ZenModePanel.ZEN_SETTINGS;
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+            AsyncTask.execute(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        // Dismiss the lock screen when Settings starts.
+                        ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+                    } catch (RemoteException e) {
+                    }
+                    final Intent intent = ZenModePanel.ZEN_SETTINGS;
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                    mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+                }
+            });
+            mDialogPanel.postDismiss(mDismissDelay);
         }
     };
 
@@ -153,7 +163,7 @@
 
         @Override
         public void dismiss() throws RemoteException {
-            mPanel.postDismiss();
+            mPanel.postDismiss(0);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 6bb9765..9c166ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -215,10 +215,12 @@
     }
 
     private void refreshExitConditionText() {
+        final String forever = mContext.getString(R.string.zen_mode_forever);
         if (mExitConditionId == null) {
-            mExitConditionText = mContext.getString(R.string.zen_mode_forever);
+            mExitConditionText = forever;
         } else if (ZenModeConfig.isValidCountdownConditionId(mExitConditionId)) {
-            mExitConditionText = parseExistingTimeCondition(mExitConditionId).summary;
+            final Condition condition = parseExistingTimeCondition(mExitConditionId);
+            mExitConditionText = condition != null ? condition.summary : forever;
         } else {
             mExitConditionText = "(until condition ends)";  // TODO persist current description
         }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 28f7a0f..ef15a80 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -5506,12 +5506,12 @@
 
         // prevent status bar interaction from clearing certain flags
         boolean statusBarHasFocus = win.getAttrs().type == TYPE_STATUS_BAR;
-        if (statusBarHasFocus) {
+        if (statusBarHasFocus && !isStatusBarKeyguard()) {
             int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_IMMERSIVE
                     | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
-            if (!isStatusBarKeyguard() || mHideLockScreen) {
+            if (mHideLockScreen) {
                 flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
             }
             vis = (vis & ~flags) | (oldVis & flags);
diff --git a/rs/java/android/renderscript/ScriptIntrinsicBlend.java b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
index d4038c2..906e0f6 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicBlend.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
@@ -99,7 +99,7 @@
      * @param opt LaunchOptions for clipping
      */
     public void forEachSrc(Allocation ain, Allocation aout, Script.LaunchOptions opt) {
-        forEachDst(ain, aout, null);
+        blend(1, ain, aout, null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ea05b98..6554ed3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -179,8 +179,6 @@
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLSession;
 
-import static android.net.ConnectivityManager.INVALID_NET_ID;
-
 /**
  * @hide
  */
@@ -1841,8 +1839,8 @@
         final int uid = Binder.getCallingUid();
         final long token = Binder.clearCallingIdentity();
         try {
-            LinkProperties lp = null;
-            int netId = INVALID_NET_ID;
+            LinkProperties lp;
+            int netId;
             synchronized (nai) {
                 lp = nai.linkProperties;
                 netId = nai.network.netId;
@@ -2124,16 +2122,6 @@
                 "ConnectivityService");
     }
 
-    private void enforceMarkNetworkSocketPermission() {
-        //Media server special case
-        if (Binder.getCallingUid() == Process.MEDIA_UID) {
-            return;
-        }
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.MARK_NETWORK_SOCKET,
-                "ConnectivityService");
-    }
-
     /**
      * Handle a {@code DISCONNECTED} event. If this pertains to the non-active
      * network, we ignore it. If it is for the active network, we send out a
@@ -2239,7 +2227,7 @@
         } catch (Exception e) {
             loge("Exception removing network: " + e);
         } finally {
-            mNetTrackers[prevNetType].setNetId(INVALID_NET_ID);
+//            mNetTrackers[prevNetType].setNetId(INVALID_NET_ID);
         }
     }
 
@@ -4086,23 +4074,6 @@
         }
     }
 
-    @Override
-    public void markSocketAsUser(ParcelFileDescriptor socket, int uid) {
-        enforceMarkNetworkSocketPermission();
-        final long token = Binder.clearCallingIdentity();
-        try {
-            int mark = mNetd.getMarkForUid(uid);
-            // Clear the mark on the socket if no mark is needed to prevent socket reuse issues
-            if (mark == -1) {
-                mark = 0;
-            }
-            NetworkUtils.markSocket(socket.getFd(), mark);
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     /**
      * Configure a TUN interface and return its file descriptor. Parameters
      * are encoded and opaque to this class. This method is used by VpnBuilder
@@ -5322,9 +5293,19 @@
         setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent);
     }
 
+    /**
+     * Sets a network sampling alarm.
+     */
     void setAlarm(int timeoutInMilliseconds, PendingIntent intent) {
         long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
+        int alarmType;
+        if (Resources.getSystem().getBoolean(
+                R.bool.config_networkSamplingWakesDevice)) {
+            alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
+        } else {
+            alarmType = AlarmManager.ELAPSED_REALTIME;
+        }
+        mAlarmManager.set(alarmType, wakeupTime, intent);
     }
 
     private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 8fb2e9f..d4f141d 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -148,7 +148,6 @@
     static final int MSG_UNBIND_METHOD = 3000;
     static final int MSG_BIND_METHOD = 3010;
     static final int MSG_SET_ACTIVE = 3020;
-    static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 3030;
     static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
 
     static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
@@ -2338,26 +2337,6 @@
         }
     }
 
-    @Override
-    public void setCursorAnchorMonitorMode(IBinder token, int monitorMode) {
-        if (DEBUG) {
-            Slog.d(TAG, "setCursorAnchorMonitorMode: monitorMode=" + monitorMode);
-        }
-        if (!calledFromValidUser()) {
-            return;
-        }
-        synchronized (mMethodMap) {
-            if (!calledWithValidToken(token)) {
-                final int uid = Binder.getCallingUid();
-                Slog.e(TAG, "Ignoring setCursorAnchorMonitorMode due to an invalid token. uid:"
-                        + uid + " token:" + token);
-                return;
-            }
-            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIO(
-                    MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, mCurClient));
-        }
-    }
-
     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
         synchronized (mMethodMap) {
             setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
@@ -2595,15 +2574,6 @@
                             + ((ClientState)msg.obj).uid);
                 }
                 return true;
-            case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE:
-                try {
-                    ((ClientState)msg.obj).client.setCursorAnchorMonitorMode(msg.arg1);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Got RemoteException sending setCursorAnchorMonitorMode "
-                            + "notification to pid " + ((ClientState)msg.obj).pid
-                            + " uid " + ((ClientState)msg.obj).uid);
-                }
-                return true;
             case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
                 final int sequenceNumber = msg.arg1;
                 final IInputMethodClient client = (IInputMethodClient)msg.obj;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 10382ba..bae2d22 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,28 @@
 
 package com.android.server;
 
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.location.ActivityRecognitionProxy;
+import com.android.server.location.FlpHardwareProvider;
+import com.android.server.location.FusedProxy;
+import com.android.server.location.GeocoderProxy;
+import com.android.server.location.GeofenceManager;
+import com.android.server.location.GeofenceProxy;
+import com.android.server.location.GpsLocationProvider;
+import com.android.server.location.GpsMeasurementsProvider;
+import com.android.server.location.LocationBlacklist;
+import com.android.server.location.LocationFudger;
+import com.android.server.location.LocationProviderInterface;
+import com.android.server.location.LocationProviderProxy;
+import com.android.server.location.LocationRequestStatistics;
+import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
+import com.android.server.location.LocationRequestStatistics.PackageStatistics;
+import com.android.server.location.MockProvider;
+import com.android.server.location.PassiveProvider;
+
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -32,10 +54,12 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.hardware.location.ActivityRecognitionHardware;
 import android.location.Address;
 import android.location.Criteria;
 import android.location.GeocoderParams;
 import android.location.Geofence;
+import android.location.IGpsMeasurementsListener;
 import android.location.IGpsStatusListener;
 import android.location.IGpsStatusProvider;
 import android.location.ILocationListener;
@@ -62,26 +86,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.location.ProviderProperties;
-import com.android.internal.location.ProviderRequest;
-import com.android.internal.os.BackgroundThread;
-import com.android.server.location.FlpHardwareProvider;
-import com.android.server.location.FusedProxy;
-import com.android.server.location.GeocoderProxy;
-import com.android.server.location.GeofenceProxy;
-import com.android.server.location.GeofenceManager;
-import com.android.server.location.GpsLocationProvider;
-import com.android.server.location.LocationBlacklist;
-import com.android.server.location.LocationFudger;
-import com.android.server.location.LocationProviderInterface;
-import com.android.server.location.LocationProviderProxy;
-import com.android.server.location.LocationRequestStatistics;
-import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
-import com.android.server.location.LocationRequestStatistics.PackageStatistics;
-import com.android.server.location.MockProvider;
-import com.android.server.location.PassiveProvider;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -154,6 +158,7 @@
     private LocationWorkerHandler mLocationHandler;
     private PassiveProvider mPassiveProvider;  // track passive provider for special cases
     private LocationBlacklist mBlacklist;
+    private GpsMeasurementsProvider mGpsMeasurementsProvider;
 
     // --- fields below are protected by mLock ---
     // Set of providers that are explicitly enabled
@@ -403,6 +408,7 @@
             addProviderLocked(gpsProvider);
             mRealProviders.put(LocationManager.GPS_PROVIDER, gpsProvider);
         }
+        mGpsMeasurementsProvider = gpsProvider.getGpsMeasurementsProvider();
 
         /*
         Load package name(s) containing location provider support.
@@ -471,7 +477,7 @@
             Slog.e(TAG,  "no geocoder provider found");
         }
 
-        // bind to fused provider if supported
+        // bind to fused hardware provider if supported
         if (FlpHardwareProvider.isSupported()) {
           FlpHardwareProvider flpHardwareProvider =
               FlpHardwareProvider.getInstance(mContext);
@@ -501,6 +507,23 @@
           Slog.e(TAG, "FLP HAL not supported.");
         }
 
+        // bind to the hardware activity recognition if supported
+        if (ActivityRecognitionHardware.isSupported()) {
+            ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
+                    mContext,
+                    mLocationHandler,
+                    ActivityRecognitionHardware.getInstance(mContext),
+                    com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
+                    com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
+                    com.android.internal.R.array.config_locationProviderPackageNames);
+
+            if (proxy == null) {
+                Slog.e(TAG, "Unable to bind ActivityRecognitionProxy.");
+            }
+        } else {
+            Slog.e(TAG, "Hardware Activity-Recognition not supported.");
+        }
+
         String[] testProviderStrings = resources.getStringArray(
                 com.android.internal.R.array.config_testLocationProviders);
         for (String testProviderString : testProviderStrings) {
@@ -1804,6 +1827,36 @@
     }
 
     @Override
+    public boolean addGpsMeasurementsListener(
+            IGpsMeasurementsListener listener,
+            String packageName) {
+        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+        checkResolutionLevelIsSufficientForProviderUse(
+                allowedResolutionLevel,
+                LocationManager.GPS_PROVIDER);
+
+        int uid = Binder.getCallingUid();
+        long identity = Binder.clearCallingIdentity();
+        boolean hasLocationAccess;
+        try {
+            hasLocationAccess = checkLocationAccess(uid, packageName, allowedResolutionLevel);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        if (!hasLocationAccess) {
+            return false;
+        }
+
+        return mGpsMeasurementsProvider.addListener(listener);
+    }
+
+    @Override
+    public boolean removeGpsMeasurementsListener(IGpsMeasurementsListener listener) {
+        return mGpsMeasurementsProvider.removeListener(listener);
+    }
+
+    @Override
     public boolean sendExtraCommand(String provider, String command, Bundle extras) {
         if (provider == null) {
             // throw NullPointerException to remain compatible with previous implementation
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c9f40cf..2434f712 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -26,7 +26,6 @@
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
 import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.GetMarkResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
@@ -144,7 +143,6 @@
         public static final int TetheringStatsResult      = 221;
         public static final int DnsProxyQueryResult       = 222;
         public static final int ClatdStatusResult         = 223;
-        public static final int GetMarkResult             = 225;
 
         public static final int InterfaceChange           = 600;
         public static final int BandwidthControl          = 601;
@@ -1750,56 +1748,6 @@
     }
 
     @Override
-    public int getMarkForUid(int uid) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("interface", "fwmark", "get", "mark", uid);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-        event.checkCode(GetMarkResult);
-        return Integer.parseInt(event.getMessage());
-    }
-
-    @Override
-    public int getMarkForProtect() {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("interface", "fwmark", "get", "protect");
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-        event.checkCode(GetMarkResult);
-        return Integer.parseInt(event.getMessage());
-    }
-
-    @Override
-    public void setMarkedForwardingRoute(String iface, RouteInfo route) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            LinkAddress dest = route.getDestinationLinkAddress();
-            mConnector.execute("interface", "fwmark", "route", "add", iface,
-                    dest.getAddress().getHostAddress(), dest.getPrefixLength());
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
-    public void clearMarkedForwardingRoute(String iface, RouteInfo route) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            LinkAddress dest = route.getDestinationLinkAddress();
-            mConnector.execute("interface", "fwmark", "route", "remove", iface,
-                    dest.getAddress().getHostAddress(), dest.getPrefixLength());
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
     public void setHostExemption(LinkAddress host) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 69262c8..d6e51f3 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -961,7 +961,7 @@
                 + " type=" + resolvedType + " callingUid=" + callingUid);
 
         userId = mAm.handleIncomingUser(callingPid, callingUid, userId,
-                false, true, "service", null);
+                false, ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
 
         ServiceMap smap = getServiceMap(userId);
         final ComponentName comp = service.getComponent();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e8e1536..cdb3835 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -41,7 +42,9 @@
 import android.os.PersistableBundle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 
+import android.util.SparseIntArray;
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsService;
@@ -343,6 +346,11 @@
     // How many bytes to write into the dropbox log before truncating
     static final int DROPBOX_MAX_SIZE = 256 * 1024;
 
+    // Access modes for handleIncomingUser.
+    static final int ALLOW_NON_FULL = 0;
+    static final int ALLOW_NON_FULL_IN_PROFILE = 1;
+    static final int ALLOW_FULL_ONLY = 2;
+
     /** All system services */
     SystemServiceManager mSystemServiceManager;
 
@@ -413,6 +421,7 @@
      * List of intents that were used to start the most recent tasks.
      */
     ArrayList<TaskRecord> mRecentTasks;
+    ArraySet<TaskRecord> mTmpRecents = new ArraySet<TaskRecord>();
 
     public class PendingAssistExtras extends Binder implements Runnable {
         public final ActivityRecord activity;
@@ -1094,6 +1103,12 @@
 
     int mCurrentUserId = 0;
     int[] mCurrentProfileIds = new int[] {UserHandle.USER_OWNER}; // Accessed by ActivityStack
+
+    /**
+     * Mapping from each known user ID to the profile group ID it is associated with.
+     */
+    SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+
     private UserManagerService mUserManager;
 
     private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -3340,7 +3355,7 @@
             String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivity");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, true, "startActivity", null);
+                false, ALLOW_FULL_ONLY, "startActivity", null);
         // TODO: Switch to user app stacks here.
         return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
                 null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
@@ -3354,12 +3369,12 @@
             ParcelFileDescriptor profileFd, Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivityAndWait");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, true, "startActivityAndWait", null);
+                false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
         WaitResult res = new WaitResult();
         // TODO: Switch to user app stacks here.
         mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
                 null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
-                res, null, options, UserHandle.getCallingUserId(), null);
+                res, null, options, userId, null);
         return res;
     }
 
@@ -3370,7 +3385,7 @@
             Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivityWithConfig");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, true, "startActivityWithConfig", null);
+                false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
                 resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -3428,7 +3443,7 @@
             throw new NullPointerException("null session or interactor");
         }
         userId = handleIncomingUser(callingPid, callingUid, userId,
-                false, true, "startVoiceActivity", null);
+                false, ALLOW_FULL_ONLY, "startVoiceActivity", null);
         // TODO: Switch to user app stacks here.
         return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
                 resolvedType, session, interactor, null, null, 0, startFlags,
@@ -3547,7 +3562,7 @@
                     IActivityContainer container) {
 
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, true, "startActivityInPackage", null);
+                false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
 
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
@@ -3562,7 +3577,7 @@
             int userId) {
         enforceNotIsolatedCaller("startActivities");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, true, "startActivity", null);
+                false, ALLOW_FULL_ONLY, "startActivity", null);
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents,
                 resolvedTypes, resultTo, options, userId);
@@ -3574,7 +3589,7 @@
             Bundle options, int userId) {
 
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, true, "startActivityInPackage", null);
+                false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes,
                 resultTo, options, userId);
@@ -3598,7 +3613,7 @@
 
         int maxRecents = task.maxRecents - 1;
         for (int i=0; i<N; i++) {
-            TaskRecord tr = mRecentTasks.get(i);
+            final TaskRecord tr = mRecentTasks.get(i);
             if (task != tr) {
                 if (task.userId != tr.userId) {
                     continue;
@@ -3631,6 +3646,9 @@
             // and their maxRecents has been reached.
             tr.disposeThumbnail();
             mRecentTasks.remove(i);
+            if (task != tr) {
+                tr.closeRecentsChain();
+            }
             i--;
             N--;
             if (task.intent == null) {
@@ -3641,7 +3659,9 @@
             mTaskPersister.notify(tr, false);
         }
         if (N >= MAX_RECENT_TASKS) {
-            mRecentTasks.remove(N-1).disposeThumbnail();
+            final TaskRecord tr = mRecentTasks.remove(N - 1);
+            tr.disposeThumbnail();
+            tr.closeRecentsChain();
         }
         mRecentTasks.add(0, task);
     }
@@ -4496,7 +4516,7 @@
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
         userId = handleIncomingUser(pid, uid,
-                userId, false, true, "clearApplicationUserData", null);
+                userId, false, ALLOW_FULL_ONLY, "clearApplicationUserData", null);
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
@@ -4564,7 +4584,7 @@
         }
 
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, true, "killBackgroundProcesses", null);
+                userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
@@ -4647,7 +4667,7 @@
         }
         final int callingPid = Binder.getCallingPid();
         userId = handleIncomingUser(callingPid, Binder.getCallingUid(),
-                userId, true, true, "forceStopPackage", null);
+                userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
@@ -5714,8 +5734,8 @@
             int callingUid = Binder.getCallingUid();
             int origUserId = userId;
             userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
-                    type == ActivityManager.INTENT_SENDER_BROADCAST, false,
-                    "getIntentSender", null);
+                    type == ActivityManager.INTENT_SENDER_BROADCAST,
+                    ALLOW_NON_FULL, "getIntentSender", null);
             if (origUserId == UserHandle.USER_CURRENT) {
                 // We don't want to evaluate this until the pending intent is
                 // actually executed.  However, we do want to always do the
@@ -7267,8 +7287,7 @@
         tr.updateTaskDescription();
 
         // Compose the recent task info
-        ActivityManager.RecentTaskInfo rti
-                = new ActivityManager.RecentTaskInfo();
+        ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
         rti.id = tr.getTopActivity() == null ? -1 : tr.taskId;
         rti.persistentId = tr.taskId;
         rti.baseIntent = new Intent(tr.getBaseIntent());
@@ -7279,19 +7298,20 @@
         rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
         rti.firstActiveTime = tr.firstActiveTime;
         rti.lastActiveTime = tr.lastActiveTime;
+        rti.affiliatedTaskId = tr.mAffiliatedTaskId;
         return rti;
     }
 
     @Override
-    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
-            int flags, int userId) {
+    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         final int callingUid = Binder.getCallingUid();
         userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
-                false, true, "getRecentTasks", null);
+                false, ALLOW_FULL_ONLY, "getRecentTasks", null);
 
+        final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
+        final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0;
         synchronized (this) {
-            final boolean allowed = checkCallingPermission(
-                    android.Manifest.permission.GET_TASKS)
+            final boolean allowed = checkCallingPermission(android.Manifest.permission.GET_TASKS)
                     == PackageManager.PERMISSION_GRANTED;
             if (!allowed) {
                 Slog.w(TAG, "getRecentTasks: caller " + callingUid
@@ -7309,12 +7329,60 @@
                             maxNum < N ? maxNum : N);
 
             final Set<Integer> includedUsers;
-            if ((flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0) {
+            if (includeProfiles) {
                 includedUsers = getProfileIdsLocked(userId);
             } else {
                 includedUsers = new HashSet<Integer>();
             }
             includedUsers.add(Integer.valueOf(userId));
+
+            // Regroup affiliated tasks together.
+            for (int i = 0; i < N; ) {
+                TaskRecord task = mRecentTasks.remove(i);
+                if (mTmpRecents.contains(task)) {
+                    continue;
+                }
+                int affiliatedTaskId = task.mAffiliatedTaskId;
+                while (true) {
+                    TaskRecord next = task.mNextAffiliate;
+                    if (next == null) {
+                        break;
+                    }
+                    if (next.mAffiliatedTaskId != affiliatedTaskId) {
+                        Slog.e(TAG, "Error in Recents: next.affiliatedTaskId=" +
+                                next.mAffiliatedTaskId + " affiliatedTaskId=" + affiliatedTaskId);
+                        task.setNextAffiliate(null);
+                        if (next.mPrevAffiliate == task) {
+                            next.setPrevAffiliate(null);
+                        }
+                        break;
+                    }
+                    if (next.mPrevAffiliate != task) {
+                        Slog.e(TAG, "Error in Recents chain prev.mNextAffiliate=" +
+                                next.mPrevAffiliate + " task=" + task);
+                        next.setPrevAffiliate(null);
+                        break;
+                    }
+                    if (!mRecentTasks.contains(next)) {
+                        Slog.e(TAG, "Error in Recents: next=" + next + " not in mRecentTasks");
+                        task.setNextAffiliate(null);
+                        if (next.mPrevAffiliate == task) {
+                            next.setPrevAffiliate(null);
+                        }
+                        break;
+                    }
+                    task = next;
+                }
+                // task is now the end of the list
+                do {
+                    mRecentTasks.remove(task);
+                    mRecentTasks.add(i++, task);
+                    mTmpRecents.add(task);
+                } while ((task = task.mPrevAffiliate) != null);
+            }
+            mTmpRecents.clear();
+            // mRecentTasks is now in sorted, affiliated order.
+
             for (int i=0; i<N && maxNum > 0; i++) {
                 TaskRecord tr = mRecentTasks.get(i);
                 // Only add calling user or related users recent tasks
@@ -7327,10 +7395,10 @@
                 // we should exclude the entry.
 
                 if (i == 0
-                        || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0)
+                        || withExcluded
                         || (tr.intent == null)
-                        || ((tr.intent.getFlags()
-                                &Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)) {
+                        || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                                == 0)) {
                     if (!allowed) {
                         // If the caller doesn't have the GET_TASKS permission, then only
                         // allow them to see a small subset of tasks -- their own and home.
@@ -7426,6 +7494,7 @@
     private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) {
         tr.disposeThumbnail();
         mRecentTasks.remove(tr);
+        tr.closeRecentsChain();
         final boolean killProcesses = (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0;
         Intent baseIntent = new Intent(
                 tr.intent != null ? tr.intent : tr.affinityIntent);
@@ -7958,14 +8027,23 @@
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
-            if (UserHandle.getUserId(callingUid) != userId) {
-                if (checkAuthorityGrants(callingUid, cpi, userId, checkUser)) {
+            int tmpTargetUserId = unsafeConvertIncomingUser(UserHandle.getUserId(callingUid));
+            if (tmpTargetUserId != userId) {
+                if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
                     return null;
                 }
                 checkedGrants = true;
             }
             userId = handleIncomingUser(callingPid, callingUid, userId,
-                    false, false, "checkContentProviderPermissionLocked " + cpi.authority, null);
+                    false, ALLOW_NON_FULL_IN_PROFILE,
+                    "checkContentProviderPermissionLocked " + cpi.authority, null);
+            if (userId != tmpTargetUserId) {
+                // When we actually went to determine the final targer user ID, this ended
+                // up different than our initial check for the authority.  This is because
+                // they had asked for USER_CURRENT_OR_SELF and we ended up switching to
+                // SELF.  So we need to re-check the grants again.
+                checkedGrants = false;
+            }
         }
         if (checkComponentPermission(cpi.readPermission, callingPid, callingUid,
                 cpi.applicationInfo.uid, cpi.exported)
@@ -8024,7 +8102,8 @@
     boolean checkAuthorityGrants(int callingUid, ProviderInfo cpi, int userId, boolean checkUser) {
         final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
         if (perms != null) {
-            for (GrantUri grantUri : perms.keySet()) {
+            for (int i=perms.size()-1; i>=0; i--) {
+                GrantUri grantUri = perms.keyAt(i);
                 if (grantUri.sourceUserId == userId || !checkUser) {
                     if (matchesProvider(grantUri.uri, cpi)) {
                         return true;
@@ -8439,7 +8518,7 @@
         enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
             "Do not have permission in call getContentProviderExternal()");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                false, true, "getContentProvider", null);
+                false, ALLOW_FULL_ONLY, "getContentProvider", null);
         return getContentProviderExternalUnchecked(name, token, userId);
     }
 
@@ -8733,7 +8812,7 @@
     public String getProviderMimeType(Uri uri, int userId) {
         enforceNotIsolatedCaller("getProviderMimeType");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, false, true, "getProviderMimeType", null);
+                userId, false, ALLOW_NON_FULL_IN_PROFILE, "getProviderMimeType", null);
         final String name = uri.getAuthority();
         final long ident = Binder.clearCallingIdentity();
         ContentProviderHolder holder = null;
@@ -9899,6 +9978,10 @@
                 return;
             }
 
+            // Make sure we have the current profile info, since it is needed for
+            // security checks.
+            updateCurrentProfileIdsLocked();
+
             if (mRecentTasks == null) {
                 mRecentTasks = mTaskPersister.restoreTasksLocked();
                 if (!mRecentTasks.isEmpty()) {
@@ -10131,7 +10214,7 @@
                                     throws RemoteException {
                             }
                         }, 0, null, null,
-                        android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+                        INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
                         true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
             } catch (Throwable t) {
                 Slog.wtf(TAG, "Failed sending first user broadcasts", t);
@@ -11578,6 +11661,17 @@
             if (dumpAll) {
                 pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
             }
+            synchronized (mUserProfileGroupIdsSelfLocked) {
+                if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+                    pw.println("  mUserProfileGroupIds:");
+                    for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+                        pw.print("    User #");
+                        pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+                        pw.print(" -> profile #");
+                        pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+                    }
+                }
+            }
         }
         if (mHomeProcess != null && (dumpPackage == null
                 || mHomeProcess.pkgList.containsKey(dumpPackage))) {
@@ -13329,6 +13423,7 @@
     // SERVICES
     // =========================================================
 
+    @Override
     public List<ActivityManager.RunningServiceInfo> getServices(int maxNum,
             int flags) {
         enforceNotIsolatedCaller("getServices");
@@ -13337,13 +13432,15 @@
         }
     }
 
+    @Override
     public PendingIntent getRunningServiceControlPanel(ComponentName name) {
         enforceNotIsolatedCaller("getRunningServiceControlPanel");
         synchronized (this) {
             return mServices.getRunningServiceControlPanelLocked(name);
         }
     }
-    
+
+    @Override
     public ComponentName startService(IApplicationThread caller, Intent service,
             String resolvedType, int userId) {
         enforceNotIsolatedCaller("startService");
@@ -13378,6 +13475,7 @@
         }
     }
 
+    @Override
     public int stopService(IApplicationThread caller, Intent service,
             String resolvedType, int userId) {
         enforceNotIsolatedCaller("stopService");
@@ -13391,6 +13489,7 @@
         }
     }
 
+    @Override
     public IBinder peekService(Intent service, String resolvedType) {
         enforceNotIsolatedCaller("peekService");
         // Refuse possible leaked file descriptors
@@ -13402,6 +13501,7 @@
         }
     }
     
+    @Override
     public boolean stopServiceToken(ComponentName className, IBinder token,
             int startId) {
         synchronized(this) {
@@ -13409,6 +13509,7 @@
         }
     }
 
+    @Override
     public void setServiceForeground(ComponentName className, IBinder token,
             int id, Notification notification, boolean removeNotification) {
         synchronized(this) {
@@ -13417,59 +13518,97 @@
         }
     }
 
+    @Override
     public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
             boolean requireFull, String name, String callerPackage) {
+        return handleIncomingUser(callingPid, callingUid, userId, allowAll,
+                requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
+    }
+
+    int unsafeConvertIncomingUser(int userId) {
+        return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
+                ? mCurrentUserId : userId;
+    }
+
+    int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+            int allowMode, String name, String callerPackage) {
         final int callingUserId = UserHandle.getUserId(callingUid);
-        if (callingUserId != userId) {
-            if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
-                if ((requireFull || checkComponentPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS,
-                        callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED)
-                        && checkComponentPermission(INTERACT_ACROSS_USERS_FULL,
-                                callingPid, callingUid, -1, true)
-                                != PackageManager.PERMISSION_GRANTED) {
-                    if (userId == UserHandle.USER_CURRENT_OR_SELF) {
-                        // In this case, they would like to just execute as their
-                        // owner user instead of failing.
-                        userId = callingUserId;
-                    } else {
-                        StringBuilder builder = new StringBuilder(128);
-                        builder.append("Permission Denial: ");
-                        builder.append(name);
-                        if (callerPackage != null) {
-                            builder.append(" from ");
-                            builder.append(callerPackage);
-                        }
-                        builder.append(" asks to run as user ");
-                        builder.append(userId);
-                        builder.append(" but is calling from user ");
-                        builder.append(UserHandle.getUserId(callingUid));
-                        builder.append("; this requires ");
-                        builder.append(INTERACT_ACROSS_USERS_FULL);
-                        if (!requireFull) {
-                            builder.append(" or ");
-                            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
-                        }
-                        String msg = builder.toString();
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
+        if (callingUserId == userId) {
+            return userId;
+        }
+
+        // Note that we may be accessing mCurrentUserId outside of a lock...
+        // shouldn't be a big deal, if this is being called outside
+        // of a locked context there is intrinsically a race with
+        // the value the caller will receive and someone else changing it.
+        // We assume that USER_CURRENT_OR_SELF will use the current user; later
+        // we will switch to the calling user if access to the current user fails.
+        int targetUserId = unsafeConvertIncomingUser(userId);
+
+        if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+            final boolean allow;
+            if (checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+                // If the caller has this permission, they always pass go.  And collect $200.
+                allow = true;
+            } else if (allowMode == ALLOW_FULL_ONLY) {
+                // We require full access, sucks to be you.
+                allow = false;
+            } else if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+                    callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+                // If the caller does not have either permission, they are always doomed.
+                allow = false;
+            } else if (allowMode == ALLOW_NON_FULL) {
+                // We are blanket allowing non-full access, you lucky caller!
+                allow = true;
+            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+                // We may or may not allow this depending on whether the two users are
+                // in the same profile.
+                synchronized (mUserProfileGroupIdsSelfLocked) {
+                    int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+                            UserInfo.NO_PROFILE_GROUP_ID);
+                    int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+                            UserInfo.NO_PROFILE_GROUP_ID);
+                    allow = callingProfile != UserInfo.NO_PROFILE_GROUP_ID
+                            && callingProfile == targetProfile;
+                }
+            } else {
+                throw new IllegalArgumentException("Unknown mode: " + allowMode);
+            }
+            if (!allow) {
+                if (userId == UserHandle.USER_CURRENT_OR_SELF) {
+                    // In this case, they would like to just execute as their
+                    // owner user instead of failing.
+                    targetUserId = callingUserId;
+                } else {
+                    StringBuilder builder = new StringBuilder(128);
+                    builder.append("Permission Denial: ");
+                    builder.append(name);
+                    if (callerPackage != null) {
+                        builder.append(" from ");
+                        builder.append(callerPackage);
                     }
+                    builder.append(" asks to run as user ");
+                    builder.append(userId);
+                    builder.append(" but is calling from user ");
+                    builder.append(UserHandle.getUserId(callingUid));
+                    builder.append("; this requires ");
+                    builder.append(INTERACT_ACROSS_USERS_FULL);
+                    if (allowMode != ALLOW_FULL_ONLY) {
+                        builder.append(" or ");
+                        builder.append(INTERACT_ACROSS_USERS);
+                    }
+                    String msg = builder.toString();
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
                 }
             }
-            if (userId == UserHandle.USER_CURRENT
-                    || userId == UserHandle.USER_CURRENT_OR_SELF) {
-                // Note that we may be accessing this outside of a lock...
-                // shouldn't be a big deal, if this is being called outside
-                // of a locked context there is intrinsically a race with
-                // the value the caller will receive and someone else changing it.
-                userId = mCurrentUserId;
-            }
-            if (!allowAll && userId < 0) {
-                throw new IllegalArgumentException(
-                        "Call does not support special user #" + userId);
-            }
         }
-        return userId;
+        if (!allowAll && targetUserId < 0) {
+            throw new IllegalArgumentException(
+                    "Call does not support special user #" + targetUserId);
+        }
+        return targetUserId;
     }
 
     boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
@@ -13479,12 +13618,12 @@
         if (UserHandle.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) {
             if ((flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
                 if (ActivityManager.checkUidPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        INTERACT_ACROSS_USERS,
                         aInfo.uid) != PackageManager.PERMISSION_GRANTED) {
                     ComponentName comp = new ComponentName(aInfo.packageName, className);
                     String msg = "Permission Denial: Component " + comp.flattenToShortString()
                             + " requests FLAG_SINGLE_USER, but app does not hold "
-                            + android.Manifest.permission.INTERACT_ACROSS_USERS;
+                            + INTERACT_ACROSS_USERS;
                     Slog.w(TAG, msg);
                     throw new SecurityException(msg);
                 }
@@ -13801,7 +13940,7 @@
             }
 
             userId = this.handleIncomingUser(callingPid, callingUid, userId,
-                    true, true, "registerReceiver", callerPackage);
+                    true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
 
             List allSticky = null;
 
@@ -14046,7 +14185,7 @@
         }
 
         userId = handleIncomingUser(callingPid, callingUid, userId,
-                true, false, "broadcast", callerPackage);
+                true, ALLOW_NON_FULL, "broadcast", callerPackage);
 
         // Make sure that the user who is receiving this broadcast is started.
         // If not, we will just skip it.
@@ -14510,8 +14649,8 @@
             throw new IllegalArgumentException("File descriptors passed in Intent");
         }
 
-        userId = handleIncomingUser(Binder.getCallingPid(),
-                Binder.getCallingUid(), userId, true, false, "removeStickyBroadcast", null);
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
 
         synchronized(this) {
             if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
@@ -14604,7 +14743,7 @@
             int userId, String abiOverride) {
         enforceNotIsolatedCaller("startInstrumentation");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, false, true, "startInstrumentation", null);
+                userId, false, ALLOW_FULL_ONLY, "startInstrumentation", null);
         // Refuse possible leaked file descriptors
         if (arguments != null && arguments.hasFileDescriptors()) {
             throw new IllegalArgumentException("File descriptors passed in Bundle");
@@ -16742,7 +16881,7 @@
 
     private ProcessRecord findProcessLocked(String process, int userId, String callName) {
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, true, callName, null);
+                userId, true, ALLOW_FULL_ONLY, callName, null);
         ProcessRecord proc = null;
         try {
             int pid = Integer.parseInt(process);
@@ -16859,6 +16998,17 @@
             currentProfileIds[i] = profiles.get(i).id;
         }
         mCurrentProfileIds = currentProfileIds;
+
+        synchronized (mUserProfileGroupIdsSelfLocked) {
+            mUserProfileGroupIdsSelfLocked.clear();
+            final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+            for (int i = 0; i < users.size(); i++) {
+                UserInfo user = users.get(i);
+                if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+                }
+            }
+        }
     }
 
     private Set getProfileIdsLocked(int userId) {
@@ -17031,7 +17181,7 @@
                                         throws RemoteException {
                                 }
                             }, 0, null, null,
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+                            INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
                             true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
                 }
             }
@@ -17350,7 +17500,7 @@
                 // Kick things off.
                 broadcastIntentLocked(null, null, stoppingIntent,
                         null, stoppingReceiver, 0, null, null,
-                        android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+                        INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
                         true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -17401,14 +17551,14 @@
 
     @Override
     public UserInfo getCurrentUser() {
-        if ((checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+        if ((checkCallingPermission(INTERACT_ACROSS_USERS)
                 != PackageManager.PERMISSION_GRANTED) && (
                 checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED)) {
             String msg = "Permission Denial: getCurrentUser() from pid="
                     + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+                    + " requires " + INTERACT_ACROSS_USERS;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
@@ -17423,12 +17573,12 @@
 
     @Override
     public boolean isUserRunning(int userId, boolean orStopped) {
-        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+        if (checkCallingPermission(INTERACT_ACROSS_USERS)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: isUserRunning() from pid="
                     + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+                    + " requires " + INTERACT_ACROSS_USERS;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
@@ -17451,12 +17601,12 @@
 
     @Override
     public int[] getRunningUserIds() {
-        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+        if (checkCallingPermission(INTERACT_ACROSS_USERS)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: isUserRunning() from pid="
                     + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+                    + " requires " + INTERACT_ACROSS_USERS;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d70e8b7..46521c5 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
 import android.os.PersistableBundle;
 import android.os.Trace;
@@ -498,7 +497,7 @@
         }
     }
 
-    void setTask(TaskRecord newTask, boolean isRoot) {
+    void setTask(TaskRecord newTask, TaskRecord taskToAffiliateWith) {
         if (task != null && task.removeActivity(this)) {
             if (task != newTask) {
                 task.stack.removeTask(task);
@@ -508,6 +507,15 @@
             }
         }
         task = newTask;
+        setTaskToAffiliateWith(taskToAffiliateWith);
+    }
+
+    void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
+        if (taskToAffiliateWith != null &&
+                launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE &&
+                launchMode != ActivityInfo.LAUNCH_SINGLE_TASK) {
+            task.setTaskToAffiliateWith(taskToAffiliateWith);
+        }
     }
 
     boolean changeWindowTranslucency(boolean toOpaque) {
@@ -1038,8 +1046,8 @@
         return null;
     }
 
-    private static String createImageFilename(ActivityRecord r) {
-        return String.valueOf(r.task.taskId) + ACTIVITY_ICON_SUFFIX + r.createTime +
+    private static String createImageFilename(ActivityRecord r, int taskId) {
+        return String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX + r.createTime +
                 TaskPersister.IMAGE_EXTENSION;
     }
 
@@ -1056,7 +1064,8 @@
         out.attribute(null, ATTR_USERID, String.valueOf(userId));
 
         if (taskDescription != null) {
-            task.saveTaskDescription(taskDescription, createImageFilename(this), out);
+            task.saveTaskDescription(taskDescription, createImageFilename(this, task.taskId),
+                    out);
         }
 
         out.startTag(null, TAG_INTENT);
@@ -1148,7 +1157,7 @@
         r.persistentState = persistentState;
 
         if (createTime >= 0) {
-            taskDescription.setIcon(TaskPersister.restoreImage(createImageFilename(r)));
+            taskDescription.setIcon(TaskPersister.restoreImage(createImageFilename(r, taskId)));
         }
         r.taskDescription = taskDescription;
         r.createTime = createTime;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c6bba22..1f92bf9 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -765,8 +765,7 @@
             return null;
         }
 
-        TaskRecord tr = who.task;
-        if (mService.getMostRecentTask() != tr || isHomeStack()) {
+        if (isHomeStack()) {
             // This is an optimization -- since we never show Home or Recents within Recents itself,
             // we can just go ahead and skip taking the screenshot if this is the home stack.  In
             // the case where the most recent task is not the task that was supplied, then the stack
@@ -2138,7 +2137,7 @@
                             + " Callers=" + Debug.getCallers(4));
                     if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
                             + " out to target's task " + target.task);
-                    p.setTask(targetTask, false);
+                    p.setTask(targetTask, null);
                     targetTask.addActivityAtBottom(p);
 
                     mWindowManager.setAppGroupId(p.appToken, targetTaskId);
@@ -2269,7 +2268,7 @@
                             + start + "-" + i + " to task=" + task + ":" + taskInsertionPoint);
                     for (int srcPos = start; srcPos >= i; --srcPos) {
                         final ActivityRecord p = activities.get(srcPos);
-                        p.setTask(task, false);
+                        p.setTask(task, null);
                         task.addActivityAtIndex(taskInsertionPoint, p);
 
                         if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + p
@@ -3886,6 +3885,7 @@
                 // Task creator asked to remove this when done, or this task was a voice
                 // interaction, so it should not remain on the recent tasks list.
                 mService.mRecentTasks.remove(task);
+                task.closeRecentsChain();
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 65afd71..bc184c6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1531,11 +1531,12 @@
         final Intent intent = r.intent;
         final int callingUid = r.launchedFromUid;
 
-        int launchFlags = intent.getFlags();
+        final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+        final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
 
+        int launchFlags = intent.getFlags();
         if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
-                (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE ||
-                        r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK)) {
+                (launchSingleInstance || launchSingleTask)) {
             // We have a conflict between the Intent and the Activity manifest, manifest wins.
             Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " +
                     "\"singleInstance\" or \"singleTask\"");
@@ -1556,6 +1557,9 @@
                     break;
             }
         }
+        final int launchBehindFlags = Intent.FLAG_ACTIVITY_LAUNCH_BEHIND |
+                Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+        final boolean affiliateTask = (launchFlags & launchBehindFlags) == launchBehindFlags;
 
         if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
             // For whatever reason this activity is being launched into a new
@@ -1617,8 +1621,7 @@
             // instance...  this new activity it is starting must go on its
             // own task.
             launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
-        } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
-                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
+        } else if (launchSingleInstance || launchSingleTask) {
             // The activity being started is a single instance...  it always
             // gets launched into its own task.
             launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1658,8 +1661,7 @@
         ActivityStack targetStack;
         if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
                 (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
-                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
-                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+                || launchSingleInstance || launchSingleTask) {
             // If bring to front is requested, and no result is requested, and
             // we can find a task that was started with this same
             // component, then instead of launching bring that one to the front.
@@ -1668,9 +1670,8 @@
                 // a SINGLE_INSTANCE activity, there can be one and only one
                 // instance of it in the history, and it is always in its own
                 // unique task, so we do a special search.
-                ActivityRecord intentActivity = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
-                        ? findTaskLocked(r)
-                        : findActivityLocked(intent, r.info);
+                ActivityRecord intentActivity = !launchSingleInstance ?
+                        findTaskLocked(r) : findActivityLocked(intent, r.info);
                 if (intentActivity != null) {
                     if (isLockTaskModeViolation(intentActivity.task)) {
                         showLockTaskToast();
@@ -1708,6 +1709,9 @@
                                 sourceStack.topActivity().task == sourceRecord.task)) {
                             // We really do want to push this one into the
                             // user's face, right now.
+                            if (affiliateTask && sourceRecord != null) {
+                                intentActivity.setTaskToAffiliateWith(sourceRecord.task);
+                            }
                             movedHome = true;
                             targetStack.moveTaskToFrontLocked(intentActivity.task, r, options);
                             if ((launchFlags &
@@ -1746,8 +1750,7 @@
                         reuseTask.performClearTaskLocked();
                         reuseTask.setIntent(r.intent, r.info);
                     } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
-                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
-                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+                            || launchSingleInstance || launchSingleTask) {
                         // In this situation we want to remove all activities
                         // from the task up to the one being started.  In most
                         // cases this means we are resetting the task to its
@@ -1848,8 +1851,7 @@
                 if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
                     if (top.app != null && top.app.thread != null) {
                         if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
-                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
-                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
+                            || launchSingleInstance || launchSingleTask) {
                             ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top,
                                     top.task);
                             // For paranoia, make sure we have correctly
@@ -1884,6 +1886,9 @@
         boolean newTask = false;
         boolean keepCurTransition = false;
 
+        TaskRecord taskToAffiliate = affiliateTask && sourceRecord != null ?
+                sourceRecord.task : null;
+
         // Should this be considered a new task?
         if (r.resultTo == null && !addingToTask
                 && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
@@ -1898,11 +1903,11 @@
                 r.setTask(targetStack.createTaskRecord(getNextTaskId(),
                         newTaskInfo != null ? newTaskInfo : r.info,
                         newTaskIntent != null ? newTaskIntent : intent,
-                        voiceSession, voiceInteractor, true), true);
+                        voiceSession, voiceInteractor, true), taskToAffiliate);
                 if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
                         r.task);
             } else {
-                r.setTask(reuseTask, true);
+                r.setTask(reuseTask, taskToAffiliate);
             }
             if (!movedHome) {
                 if ((launchFlags &
@@ -1914,7 +1919,7 @@
                 }
             }
         } else if (sourceRecord != null) {
-            TaskRecord sourceTask = sourceRecord.task;
+            final TaskRecord sourceTask = sourceRecord.task;
             if (isLockTaskModeViolation(sourceTask)) {
                 Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
                 return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
@@ -1922,8 +1927,7 @@
             targetStack = sourceTask.stack;
             targetStack.moveToFront();
             mWindowManager.moveTaskToTop(targetStack.topTask().taskId);
-            if (!addingToTask &&
-                    (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+            if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
                 // In this case, we are adding the activity to an existing
                 // task, but the caller has asked to clear that task if the
                 // activity is already running.
@@ -1963,7 +1967,7 @@
             // An existing activity is starting this new activity, so we want
             // to keep the new one in the same task as the one that is starting
             // it.
-            r.setTask(sourceTask, false);
+            r.setTask(sourceTask, null);
             if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                     + " in existing task " + r.task + " from source " + sourceRecord);
 
@@ -1975,7 +1979,7 @@
             targetStack.moveToFront();
             ActivityRecord prev = targetStack.topActivity();
             r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),
-                            r.info, intent, null, null, true), true);
+                            r.info, intent, null, null, true), null);
             mWindowManager.moveTaskToTop(r.task.taskId);
             if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                     + " in new guessed " + r.task);
@@ -3407,7 +3411,8 @@
         public final int startActivity(Intent intent) {
             mService.enforceNotIsolatedCaller("ActivityContainer.startActivity");
             int userId = mService.handleIncomingUser(Binder.getCallingPid(),
-                    Binder.getCallingUid(), mCurrentUser, false, true, "ActivityContainer", null);
+                    Binder.getCallingUid(), mCurrentUser, false,
+                    ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
             // TODO: Switch to user app stacks here.
             intent.addFlags(FORCE_NEW_TASK_FLAGS);
             String mimeType = intent.getType();
@@ -3433,7 +3438,8 @@
 
         private void checkEmbeddedAllowedInner(Intent intent, String resolvedType) {
             int userId = mService.handleIncomingUser(Binder.getCallingPid(),
-                    Binder.getCallingUid(), mCurrentUser, false, true, "ActivityContainer", null);
+                    Binder.getCallingUid(), mCurrentUser, false,
+                    ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
             if (resolvedType == null) {
                 resolvedType = intent.getType();
                 if (resolvedType == null && intent.getData() != null
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 1982d7e..38eef208 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import android.app.ActivityManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
@@ -151,6 +150,20 @@
         }
     }
 
+    private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
+        if (taskId < 0) {
+            return null;
+        }
+        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = tasks.get(taskNdx);
+            if (task.taskId == taskId) {
+                return task;
+            }
+        }
+        Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
+        return null;
+    }
+
     ArrayList<TaskRecord> restoreTasksLocked() {
         final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
@@ -201,7 +214,7 @@
                     XmlUtils.skipCurrentTag(in);
                 }
             } catch (Exception e) {
-                Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e);
+                Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
                 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
                 deleteFile = true;
             } finally {
@@ -222,6 +235,13 @@
             removeObsoleteFiles(recoveredTaskIds);
         }
 
+        // Fixup task affiliation from taskIds
+        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = tasks.get(taskNdx);
+            task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
+            task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
+        }
+
         TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
         tasks.toArray(tasksArray);
         Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c9890cd3cd..76373df 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -64,6 +64,9 @@
     private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
     private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
     private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
+    private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
+    private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
+    private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
     private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
 
     private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
@@ -132,14 +135,22 @@
     private final String mFilename;
     CharSequence lastDescription; // Last description captured for this item.
 
+    int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
+    TaskRecord mPrevAffiliate; // previous task in affiliated chain.
+    int mPrevAffiliateTaskId = -1; // previous id for persistence.
+    TaskRecord mNextAffiliate; // next task in affiliated chain.
+    int mNextAffiliateTaskId = -1; // next id for persistence.
+
     final ActivityManagerService mService;
 
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION;
+        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
+                TaskPersister.IMAGE_EXTENSION;
         mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
         taskId = _taskId;
+        mAffiliatedTaskId = _taskId;
         voiceSession = _voiceSession;
         voiceInteractor = _voiceInteractor;
         mActivities = new ArrayList<ActivityRecord>();
@@ -151,9 +162,11 @@
             boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
             String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime,
             long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity,
-            ActivityManager.TaskDescription _lastTaskDescription) {
+            ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
+            int prevTaskId, int nextTaskId) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION;
+        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
+                TaskPersister.IMAGE_EXTENSION;
         mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
         taskId = _taskId;
         intent = _intent;
@@ -175,6 +188,9 @@
         mLastTimeMoved = lastTimeMoved;
         mNeverRelinquishIdentity = neverRelinquishIdentity;
         lastTaskDescription = _lastTaskDescription;
+        mAffiliatedTaskId = taskAffiliation;
+        mPrevAffiliateTaskId = prevTaskId;
+        mNextAffiliateTaskId = nextTaskId;
     }
 
     void touchActiveTime() {
@@ -257,6 +273,50 @@
         return mTaskToReturnTo;
     }
 
+    void setPrevAffiliate(TaskRecord prevAffiliate) {
+        mPrevAffiliate = prevAffiliate;
+        mPrevAffiliateTaskId = prevAffiliate == null ? -1 : prevAffiliate.taskId;
+    }
+
+    void setNextAffiliate(TaskRecord nextAffiliate) {
+        mNextAffiliate = nextAffiliate;
+        mNextAffiliateTaskId = nextAffiliate == null ? -1 : nextAffiliate.taskId;
+    }
+
+    // Close up recents linked list.
+    void closeRecentsChain() {
+        if (mPrevAffiliate != null) {
+            mPrevAffiliate.setNextAffiliate(mNextAffiliate);
+        }
+        if (mNextAffiliate != null) {
+            mNextAffiliate.setPrevAffiliate(mPrevAffiliate);
+        }
+        setPrevAffiliate(null);
+        setNextAffiliate(null);
+    }
+
+    void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
+        closeRecentsChain();
+        mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
+        // Find the end
+        while (taskToAffiliateWith.mNextAffiliate != null) {
+            final TaskRecord nextRecents = taskToAffiliateWith.mNextAffiliate;
+            if (nextRecents.mAffiliatedTaskId != mAffiliatedTaskId) {
+                Slog.e(TAG, "setTaskToAffiliateWith: nextRecents=" + nextRecents + " affilTaskId="
+                        + nextRecents.mAffiliatedTaskId + " should be " + mAffiliatedTaskId);
+                if (nextRecents.mPrevAffiliate == taskToAffiliateWith) {
+                    nextRecents.setPrevAffiliate(null);
+                }
+                taskToAffiliateWith.setNextAffiliate(null);
+                break;
+            }
+            taskToAffiliateWith = nextRecents;
+        }
+        taskToAffiliateWith.setNextAffiliate(this);
+        setPrevAffiliate(taskToAffiliateWith);
+        setNextAffiliate(null);
+    }
+
     void setLastThumbnail(Bitmap thumbnail) {
         mLastThumbnail = thumbnail;
         if (thumbnail == null) {
@@ -681,11 +741,13 @@
         if (lastDescription != null) {
             out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
         }
-
         if (lastTaskDescription != null) {
             saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
                     LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
         }
+        out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
+        out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
+        out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
 
         if (affinityIntent != null) {
             out.startTag(null, TAG_AFFINITYINTENT);
@@ -733,6 +795,9 @@
         int taskId = -1;
         final int outerDepth = in.getDepth();
         ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
+        int taskAffiliation = -1;
+        int prevTaskId = -1;
+        int nextTaskId = -1;
 
         for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
             final String attrName = in.getAttributeName(attrNdx);
@@ -767,6 +832,12 @@
                 neverRelinquishIdentity = Boolean.valueOf(attrValue);
             } else if (readTaskDescriptionAttribute(taskDescription, attrName, attrValue)) {
                 // Completed in TaskPersister.readTaskDescriptionAttribute()
+            } else if (ATTR_TASK_AFFILIATION.equals(attrName)) {
+                taskAffiliation = Integer.valueOf(attrValue);
+            } else if (ATTR_PREV_AFFILIATION.equals(attrName)) {
+                prevTaskId = Integer.valueOf(attrValue);
+            } else if (ATTR_NEXT_AFFILIATION.equals(attrName)) {
+                nextTaskId = Integer.valueOf(attrValue);
             } else {
                 Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
             }
@@ -806,7 +877,8 @@
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
                 askedCompatMode, taskType, userId, lastDescription, activities, firstActiveTime,
-                lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription);
+                lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription,
+                taskAffiliation, prevTaskId, nextTaskId);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             activities.get(activityNdx).task = task;
@@ -867,6 +939,10 @@
                 pw.print(" lastActiveTime="); pw.print(lastActiveTime);
                 pw.print(" (inactive for ");
                 pw.print((getInactiveDuration()/1000)); pw.println("s)");
+        pw.print(prefix); pw.print("isPersistable="); pw.print(isPersistable);
+                pw.print(" affiliation="); pw.print(mAffiliatedTaskId);
+                pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
+                pw.print(" nextAffiliation="); pw.println(mNextAffiliateTaskId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 2c8c1c1..432424b 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -66,7 +66,7 @@
         }
         HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
         if (device == null) {
-            tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath));
+            tv.startNewDeviceAction(activeAddress, activePath);
         }
 
         int currentActive = tv.getActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 1cdf44c..3028100 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -203,5 +203,8 @@
     //       in config.xml to allow customization.
     static final int IRT_MS = 300;
 
+    static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "hdmi_cec.preferred_address.playback";
+    static final String PROPERTY_PREFERRED_ADDRESS_TV = "hdmi_cec.preferred_address.tv";
+
     private Constants() { /* cannot be instantiated */ }
 }
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 7d15f4c..b7b2f90 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -18,11 +18,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -59,6 +61,8 @@
     // Timer that manages timeout events.
     protected ActionTimer mActionTimer;
 
+    private ArrayList<Pair<FeatureAction, Runnable>> mOnFinishedCallbacks;
+
     FeatureAction(HdmiCecLocalDevice source) {
         mSource = source;
         mService = mSource.getService();
@@ -220,8 +224,22 @@
      * Finish up the action. Reset the state, and remove itself from the action queue.
      */
     protected void finish() {
+        finish(true);
+    }
+
+    void finish(boolean removeSelf) {
         clear();
-        removeAction(this);
+        if (removeSelf) {
+            removeAction(this);
+        }
+        if (mOnFinishedCallbacks != null) {
+            for (Pair<FeatureAction, Runnable> actionCallbackPair: mOnFinishedCallbacks) {
+                if (actionCallbackPair.first.mState != STATE_NONE) {
+                    actionCallbackPair.second.run();
+                }
+            }
+            mOnFinishedCallbacks = null;
+        }
     }
 
     protected final HdmiCecLocalDevice localDevice() {
@@ -244,10 +262,17 @@
         return mSource.getDeviceInfo().getPhysicalAddress();
     }
 
-    protected void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
+    protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
         sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
                 getSourceAddress(), targetAddress, uiCommand));
         sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
                 getSourceAddress(), targetAddress));
     }
+
+    protected final void addOnFinishedCallback(FeatureAction action, Runnable runnable) {
+        if (mOnFinishedCallbacks == null) {
+            mOnFinishedCallbacks = new ArrayList<>();
+        }
+        mOnFinishedCallbacks.add(Pair.create(action, runnable));
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 14d9b75..72519f2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -265,8 +265,6 @@
     @ServiceThreadOnly
     void clearLogicalAddress() {
         assertRunOnServiceThread();
-        // TODO: consider to backup logical address so that new logical address
-        // allocation can use it as preferred address.
         for (int i = 0; i < mLocalDevices.size(); ++i) {
             mLocalDevices.valueAt(i).clearAddress();
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 3937ce1..f1e7ff2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -20,6 +20,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -65,7 +66,7 @@
     // Note that access to this collection should happen in service thread.
     private final LinkedList<FeatureAction> mActions = new LinkedList<>();
 
-    private Handler mHandler = new Handler () {
+    private final Handler mHandler = new Handler () {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -108,8 +109,7 @@
     @ServiceThreadOnly
     void init() {
         assertRunOnServiceThread();
-        mPreferredAddress = Constants.ADDR_UNREGISTERED;
-        // TODO: load preferred address from permanent storage.
+        mPreferredAddress = getPreferredAddress();
     }
 
     /**
@@ -118,6 +118,16 @@
     protected abstract void onAddressAllocated(int logicalAddress, boolean fromBootup);
 
     /**
+     * Get the preferred logical address from system properties.
+     */
+    protected abstract int getPreferredAddress();
+
+    /**
+     * Set the preferred logical address to system properties.
+     */
+    protected abstract void setPreferredAddress(int addr);
+
+    /**
      * Dispatch incoming message.
      *
      * @param message incoming message
@@ -398,6 +408,7 @@
         assertRunOnServiceThread();
         mAddress = mPreferredAddress = logicalAddress;
         onAddressAllocated(logicalAddress, fromBootup);
+        setPreferredAddress(logicalAddress);
     }
 
     @ServiceThreadOnly
@@ -427,18 +438,6 @@
     }
 
     @ServiceThreadOnly
-    void setPreferredAddress(int addr) {
-        assertRunOnServiceThread();
-        mPreferredAddress = addr;
-    }
-
-    @ServiceThreadOnly
-    int getPreferredAddress() {
-        assertRunOnServiceThread();
-        return mPreferredAddress;
-    }
-
-    @ServiceThreadOnly
     void addAndStartAction(final FeatureAction action) {
         assertRunOnServiceThread();
         if (mService.isPowerStandbyOrTransient()) {
@@ -482,6 +481,7 @@
     @ServiceThreadOnly
     void removeAction(final FeatureAction action) {
         assertRunOnServiceThread();
+        action.finish(false);
         mActions.remove(action);
         checkIfPendingActionsCleared();
     }
@@ -502,8 +502,8 @@
         while (iter.hasNext()) {
             FeatureAction action = iter.next();
             if (action != exception && action.getClass().equals(clazz)) {
-                action.clear();
-                mActions.remove(action);
+                action.finish(false);
+                iter.remove();
             }
         }
         checkIfPendingActionsCleared();
@@ -610,7 +610,7 @@
      * Called when the system goes to standby mode.
      *
      * @param initiatedByCec true if this power sequence is initiated
-     *         by the reception the CEC messages like &lt;Standby&gt;
+     *        by the reception the CEC messages like &lt;Standby&gt;
      */
     protected void onStandby(boolean initiatedByCec) {}
 
@@ -619,11 +619,19 @@
      * actions are completed or timeout is issued.
      *
      * @param initiatedByCec true if this sequence is initiated
-     *         by the reception the CEC messages like &lt;Standby&gt;
-     * @param callback callback interface to get notified when all pending actions are cleared
+     *        by the reception the CEC messages like &lt;Standby&gt;
+     * @param origialCallback callback interface to get notified when all pending actions are
+     *        cleared
      */
-    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
-        mPendingActionClearedCallback = callback;
+    protected void disableDevice(boolean initiatedByCec,
+            final PendingActionClearedCallback origialCallback) {
+        mPendingActionClearedCallback = new PendingActionClearedCallback() {
+            @Override
+            public void onCleared(HdmiCecLocalDevice device) {
+                mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
+                origialCallback.onCleared(device);
+            }
+        };
         mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
                 DEVICE_CLEANUP_TIMEOUT);
     }
@@ -637,7 +645,7 @@
         Iterator<FeatureAction> iter = mActions.iterator();
         while (iter.hasNext()) {
             FeatureAction action = iter.next();
-            action.finish();
+            action.finish(false);
             iter.remove();
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 06907ce..a66d78c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -20,6 +20,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Slog;
 
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -44,6 +45,22 @@
                 mAddress, mService.getPhysicalAddress(), mDeviceType));
     }
 
+    @Override
+    @ServiceThreadOnly
+    protected int getPreferredAddress() {
+        assertRunOnServiceThread();
+        return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+                Constants.ADDR_UNREGISTERED);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected void setPreferredAddress(int addr) {
+        assertRunOnServiceThread();
+        SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+                String.valueOf(addr));
+    }
+
     @ServiceThreadOnly
     void oneTouchPlay(IHdmiControlCallback callback) {
         assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 923b87d..44b0f28eb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -20,11 +20,13 @@
 import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.IHdmiControlCallback;
+import android.media.AudioManager;
 import android.media.AudioManager.OnAudioPortUpdateListener;
 import android.media.AudioPatch;
 import android.media.AudioPort;
 import android.media.AudioSystem;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.util.Slog;
@@ -107,6 +109,22 @@
         // TODO: unregister audio port update listener if local device is released.
     }
 
+    @Override
+    @ServiceThreadOnly
+    protected int getPreferredAddress() {
+        assertRunOnServiceThread();
+        return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
+                Constants.ADDR_UNREGISTERED);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected void setPreferredAddress(int addr) {
+        assertRunOnServiceThread();
+        SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
+                String.valueOf(addr));
+    }
+
     private void registerAudioPortUpdateListener() {
         mService.getAudioManager().registerAudioPortUpdateListener(
                 new OnAudioPortUpdateListener() {
@@ -144,6 +162,11 @@
             handleSelectInternalSource(callback);
             return;
         }
+        if (!mService.isControlEnabled()) {
+            setActiveSource(targetAddress);
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+            return;
+        }
         HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
         if (targetDevice == null) {
             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
@@ -222,18 +245,23 @@
     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         // Seq #20
-        if (!mService.isControlEnabled() || portId == getActivePortId()) {
+        if (!mService.isValidPortId(portId)) {
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
         }
-        // TODO: Make sure this call does not stem from <Active Source> message reception.
-
+        if (!mService.isControlEnabled()) {
+            setActivePortId(portId);
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+            return;
+        }
+        if (portId == getActivePortId()) {
+            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
+            return;
+        }
         setActivePortId(portId);
         // TODO: Return immediately if the operation is triggered by <Text/Image View On>
-        //       and this is the first notification about the active input after power-on.
-        // TODO: Handle invalid port id / active input which should be treated as an
-        //       internal tuner.
-
+        //       and this is the first notification about the active input after power-on
+        //       (switch to HDMI didn't happen so far but is expected to happen soon).
         removeAction(RoutingControlAction.class);
 
         int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId()));
@@ -371,10 +399,28 @@
         if (!isInDeviceList(path, address)) {
             handleNewDeviceAtTheTailOfActivePath(path);
         }
-        addAndStartAction(new NewDeviceAction(this, address, path));
+        startNewDeviceAction(address, path);
         return true;
     }
 
+    void startNewDeviceAction(int address, int path) {
+        for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
+            // If there is new device action which has the same logical address and path
+            // ignore new request.
+            // NewDeviceAction is created whenever it receives <Report Physical Address>.
+            // And there is a chance starting NewDeviceAction for the same source.
+            // Usually, new device sends <Report Physical Address> when it's plugged
+            // in. However, TV can detect a new device from HotPlugDetectionAction,
+            // which sends <Give Physical Address> to the source for newly detected
+            // device.
+            if (action.isActionOf(address, path)) {
+                return;
+            }
+        }
+
+        addAndStartAction(new NewDeviceAction(this, address, path));
+    }
+
     private void handleNewDeviceAtTheTailOfActivePath(int path) {
         // Seq #22
         if (isTailOfActivePath(path, getActivePath())) {
@@ -536,10 +582,17 @@
     }
 
     @ServiceThreadOnly
+    // Seq #32
     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
+        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
+            setSystemAudioMode(false, true);
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+            return;
+        }
         HdmiCecDeviceInfo avr = getAvrDeviceInfo();
         if (avr == null) {
+            setSystemAudioMode(false, true);
             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
             return;
         }
@@ -654,8 +707,10 @@
         synchronized (mLock) {
             mSystemAudioMute = mute;
             mSystemAudioVolume = volume;
-            // TODO: pass volume to service (audio service) after scale it to local volume level.
-            mService.setAudioStatus(mute, volume);
+            int maxVolume = mService.getAudioManager().getStreamMaxVolume(
+                    AudioManager.STREAM_MUSIC);
+            mService.setAudioStatus(mute,
+                    VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a2a795c..a9a391b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -398,6 +398,15 @@
         return Constants.INVALID_PORT_ID;
     }
 
+    boolean isValidPortId(int portId) {
+        for (HdmiPortInfo info : mPortInfo) {
+            if (portId == info.getId()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns {@link Looper} for IO operation.
      *
@@ -577,7 +586,22 @@
     }
 
     void setAudioStatus(boolean mute, int volume) {
-        // TODO: Hook up with AudioManager.
+        AudioManager audioManager = getAudioManager();
+        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
+        if (mute) {
+            if (!muted) {
+                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
+            }
+        } else {
+            if (muted) {
+                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
+            }
+            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
+            // volume change notification back to hdmi control service.
+            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
+                    AudioManager.FLAG_SHOW_UI |
+                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
+        }
     }
 
     void announceSystemAudioModeChange(boolean enabled) {
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 185de59f..6204c16 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -219,7 +219,7 @@
             return;
         }
 
-        // Should ave only one Device Select Action
+        // Should have only one Device Select Action
         DeviceSelectAction action = actions.get(0);
         if (action.getTargetAddress() == address) {
             removeAction(DeviceSelectAction.class);
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 80deaab..f89e299 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -56,7 +56,6 @@
      * @param source {@link HdmiCecLocalDevice} instance
      * @param deviceLogicalAddress logical address of the device in interest
      * @param devicePhysicalAddress physical address of the device in interest
-     * @param requireRoutingChange whether to initiate routing change or not
      */
     NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress,
             int devicePhysicalAddress) {
@@ -68,18 +67,6 @@
 
     @Override
     public boolean start() {
-        if (HdmiUtils.getTypeFromAddress(getSourceAddress())
-                == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
-            if (tv().getAvrDeviceInfo() == null) {
-                // TODO: Start system audio initiation action
-            }
-
-            if (shouldTryArcInitiation()) {
-                addAndStartAction(new RequestArcInitiationAction(localDevice(),
-                                mDeviceLogicalAddress));
-            }
-        }
-
         mState = STATE_WAITING_FOR_SET_OSD_NAME;
         if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) {
             return true;
@@ -91,10 +78,6 @@
         return true;
     }
 
-    private boolean shouldTryArcInitiation() {
-         return tv().isConnectedToArcPort(mDevicePhysicalAddress) && tv().isArcFeatureEnabled();
-    }
-
     @Override
     public boolean processCommand(HdmiCecMessage cmd) {
         // For the logical device in interest, we want two more pieces of information -
@@ -172,6 +155,23 @@
                 mDeviceLogicalAddress, mDevicePhysicalAddress,
                 HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress),
                 mVendorId, mDisplayName));
+
+        if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
+                == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
+            if (tv().getSystemAudioMode()) {
+                addAndStartAction(new SystemAudioAutoInitiationAction(localDevice(),
+                        mDeviceLogicalAddress));
+            }
+
+            if (shouldTryArcInitiation()) {
+                addAndStartAction(new RequestArcInitiationAction(localDevice(),
+                        mDeviceLogicalAddress));
+            }
+        }
+    }
+
+    private boolean shouldTryArcInitiation() {
+        return tv().isConnectedToArcPort(mDevicePhysicalAddress) && tv().isArcFeatureEnabled();
     }
 
     @Override
@@ -188,4 +188,8 @@
             finish();
         }
     }
+
+    boolean isActionOf(int address, int path) {
+        return (mDeviceLogicalAddress == address) && (mDevicePhysicalAddress == path);
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 86895cc..4be036a 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -23,14 +23,20 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import java.util.List;
+
 /**
  * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr.
  */
 abstract class SystemAudioAction extends FeatureAction {
     private static final String TAG = "SystemAudioAction";
 
+    // Transient state to differentiate with STATE_NONE where the on-finished callback
+    // will not be called.
+    private static final int STATE_CHECK_ROUTING_IN_PRGRESS = 1;
+
     // State in which waits for <SetSystemAudioMode>.
-    private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1;
+    private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 2;
 
     private static final int MAX_SEND_RETRY_COUNT = 2;
 
@@ -65,7 +71,25 @@
         mCallback = callback;
     }
 
+    // Seq #27
     protected void sendSystemAudioModeRequest() {
+        mState = STATE_CHECK_ROUTING_IN_PRGRESS;
+        List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class);
+        if (!routingActions.isEmpty()) {
+            // Should have only one Routing Control Action
+            RoutingControlAction routingAction = routingActions.get(0);
+            routingAction.addOnFinishedCallback(this, new Runnable() {
+                @Override
+                public void run() {
+                    sendSystemAudioModeRequestInternal();
+                }
+            });
+            return;
+        }
+        sendSystemAudioModeRequestInternal();
+    }
+
+    private void sendSystemAudioModeRequestInternal() {
         int avrPhysicalAddress = tv().getAvrDeviceInfo().getPhysicalAddress();
         HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
                 getSourceAddress(),
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index a565077..77783b7 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -23,7 +23,7 @@
 /**
  * Feature action that handles System Audio initiated by AVR devices.
  */
-// # Seq 33
+// Seq #33
 final class SystemAudioActionFromAvr extends SystemAudioAction {
     /**
      * Constructor
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
index 2146c4e..cb3588c 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -24,6 +24,7 @@
  * Feature action that handles System Audio initiated by TV devices.
  */
 final class SystemAudioActionFromTv extends SystemAudioAction {
+
     /**
      * Constructor
      *
@@ -41,9 +42,6 @@
 
     @Override
     boolean start() {
-        // TODO: Check HDMI-CEC is enabled.
-        // TODO: Move to the waiting state if currently a routing change is in progress.
-
         removeSystemAudioActionInProgress();
         sendSystemAudioModeRequest();
         return true;
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
index 80954d4..d03634e 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -21,6 +21,7 @@
 /**
  * Action to initiate system audio once AVR is detected on Device discovery action.
  */
+// Seq #27
 final class SystemAudioAutoInitiationAction extends FeatureAction {
     private final int mAvrAddress;
 
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
new file mode 100644
index 0000000..7e7f2e4
--- /dev/null
+++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import com.android.server.ServiceWatcher;
+
+import android.content.Context;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
+ *
+ * @hide
+ */
+public class ActivityRecognitionProxy {
+    private final String TAG = "ActivityRecognitionProxy";
+    private final ServiceWatcher mServiceWatcher;
+    private final ActivityRecognitionHardware mActivityRecognitionHardware;
+
+    private ActivityRecognitionProxy(
+            Context context,
+            Handler handler,
+            ActivityRecognitionHardware activityRecognitionHardware,
+            int overlaySwitchResId,
+            int defaultServicePackageNameResId,
+            int initialPackageNameResId) {
+        mActivityRecognitionHardware = activityRecognitionHardware;
+
+        Runnable newServiceWork = new Runnable() {
+            @Override
+            public void run() {
+                bindProvider(mActivityRecognitionHardware);
+            }
+        };
+
+        // prepare the connection to the provider
+        mServiceWatcher = new ServiceWatcher(
+                context,
+                TAG,
+                "com.android.location.service.ActivityRecognitionProvider",
+                overlaySwitchResId,
+                defaultServicePackageNameResId,
+                initialPackageNameResId,
+                newServiceWork,
+                handler);
+    }
+
+    /**
+     * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
+     *
+     * @return An instance of the proxy if it could be bound, null otherwise.
+     */
+    public static ActivityRecognitionProxy createAndBind(
+            Context context,
+            Handler handler,
+            ActivityRecognitionHardware activityRecognitionHardware,
+            int overlaySwitchResId,
+            int defaultServicePackageNameResId,
+            int initialPackageNameResId) {
+        ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy(
+                context,
+                handler,
+                activityRecognitionHardware,
+                overlaySwitchResId,
+                defaultServicePackageNameResId,
+                initialPackageNameResId);
+
+        // try to bind the provider
+        if (!activityRecognitionProxy.mServiceWatcher.start()) {
+            return null;
+        }
+
+        return activityRecognitionProxy;
+    }
+
+    /**
+     * Helper function to bind the FusedLocationHardware to the appropriate FusedProvider instance.
+     */
+    private void bindProvider(ActivityRecognitionHardware activityRecognitionHardware) {
+        IActivityRecognitionHardwareWatcher watcher =
+                IActivityRecognitionHardwareWatcher.Stub.asInterface(mServiceWatcher.getBinder());
+        if (watcher == null) {
+            Log.e(TAG, "No provider instance found on connection.");
+            return;
+        }
+
+        try {
+            watcher.onInstanceChanged(mActivityRecognitionHardware);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error delivering hardware interface.", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index c5b6c7b..8222155 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -16,6 +16,15 @@
 
 package com.android.server.location;
 
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -28,6 +37,7 @@
 import android.hardware.location.GeofenceHardwareImpl;
 import android.location.Criteria;
 import android.location.FusedBatchOptions;
+import android.location.GpsMeasurementsEvent;
 import android.location.IGpsGeofenceHardware;
 import android.location.IGpsStatusListener;
 import android.location.IGpsStatusProvider;
@@ -46,7 +56,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
@@ -64,27 +73,17 @@
 import android.util.Log;
 import android.util.NtpTrustedTime;
 
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.location.GpsNetInitiatedHandler;
-import com.android.internal.location.ProviderProperties;
-import com.android.internal.location.ProviderRequest;
-import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringReader;
-import java.util.ArrayList;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.Date;
 import java.util.Map.Entry;
 import java.util.Properties;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
 
 /**
  * A GPS implementation of LocationProvider used by LocationManager.
@@ -314,7 +313,12 @@
     private final ILocationManager mILocationManager;
     private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
     private Bundle mLocationExtras = new Bundle();
-    private ArrayList<Listener> mListeners = new ArrayList<Listener>();
+    private GpsStatusListenerHelper mListenerHelper = new GpsStatusListenerHelper() {
+        @Override
+        protected boolean isSupported() {
+            return native_is_measurement_supported();
+        }
+    };
 
     // Handler for processing events
     private Handler mHandler;
@@ -348,49 +352,29 @@
     private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
         @Override
         public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
-            if (listener == null) {
-                throw new NullPointerException("listener is null in addGpsStatusListener");
-            }
-
-            synchronized (mListeners) {
-                IBinder binder = listener.asBinder();
-                int size = mListeners.size();
-                for (int i = 0; i < size; i++) {
-                    Listener test = mListeners.get(i);
-                    if (binder.equals(test.mListener.asBinder())) {
-                        // listener already added
-                        return;
-                    }
-                }
-
-                Listener l = new Listener(listener);
-                binder.linkToDeath(l, 0);
-                mListeners.add(l);
-            }
+            mListenerHelper.addListener(listener);
         }
 
         @Override
         public void removeGpsStatusListener(IGpsStatusListener listener) {
-            if (listener == null) {
-                throw new NullPointerException("listener is null in addGpsStatusListener");
-            }
+            mListenerHelper.removeListener(listener);
+        }
+    };
 
-            synchronized (mListeners) {
-                IBinder binder = listener.asBinder();
-                Listener l = null;
-                int size = mListeners.size();
-                for (int i = 0; i < size && l == null; i++) {
-                    Listener test = mListeners.get(i);
-                    if (binder.equals(test.mListener.asBinder())) {
-                        l = test;
-                    }
-                }
+    private final GpsMeasurementsProvider mGpsMeasurementsProvider = new GpsMeasurementsProvider() {
+        @Override
+        public boolean isSupported() {
+            return native_is_measurement_supported();
+        }
 
-                if (l != null) {
-                    mListeners.remove(l);
-                    binder.unlinkToDeath(l, 0);
-                }
-            }
+        @Override
+        protected void onFirstListenerAdded() {
+            native_start_measurement_collection();
+        }
+
+        @Override
+        protected void onLastListenerRemoved() {
+            native_stop_measurement_collection();
         }
     };
 
@@ -402,6 +386,10 @@
         return mGpsGeofenceBinder;
     }
 
+    public GpsMeasurementsProvider getGpsMeasurementsProvider() {
+        return mGpsMeasurementsProvider;
+    }
+
     private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -892,26 +880,6 @@
         }
     }
 
-    private final class Listener implements IBinder.DeathRecipient {
-        final IGpsStatusListener mListener;
-
-        Listener(IGpsStatusListener listener) {
-            mListener = listener;
-        }
-
-        @Override
-        public void binderDied() {
-            if (DEBUG) Log.d(TAG, "GPS status listener died");
-
-            synchronized (mListeners) {
-                mListeners.remove(this);
-            }
-            if (mListener != null) {
-                mListener.asBinder().unlinkToDeath(this, 0);
-            }
-        }
-    }
-
     private void updateClientUids(WorkSource source) {
         // Update work source.
         WorkSource[] changes = mClientSource.setReturningDiffs(source);
@@ -1185,20 +1153,7 @@
             if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix);
 
             // notify status listeners
-            synchronized (mListeners) {
-                int size = mListeners.size();
-                for (int i = 0; i < size; i++) {
-                    Listener listener = mListeners.get(i);
-                    try {
-                        listener.mListener.onFirstFix(mTimeToFirstFix);
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "RemoteException in stopNavigating");
-                        mListeners.remove(listener);
-                        // adjust for size of list changing
-                        size--;
-                    }
-                }
-            }
+            mListenerHelper.onFirstFix(mTimeToFirstFix);
         }
 
         if (mSingleShot) {
@@ -1232,49 +1187,31 @@
     private void reportStatus(int status) {
         if (DEBUG) Log.v(TAG, "reportStatus status: " + status);
 
-        synchronized (mListeners) {
-            boolean wasNavigating = mNavigating;
+        boolean wasNavigating = mNavigating;
+        switch (status) {
+            case GPS_STATUS_SESSION_BEGIN:
+                mNavigating = true;
+                mEngineOn = true;
+                break;
+            case GPS_STATUS_SESSION_END:
+                mNavigating = false;
+                break;
+            case GPS_STATUS_ENGINE_ON:
+                mEngineOn = true;
+                break;
+            case GPS_STATUS_ENGINE_OFF:
+                mEngineOn = false;
+                mNavigating = false;
+                break;
+        }
 
-            switch (status) {
-                case GPS_STATUS_SESSION_BEGIN:
-                    mNavigating = true;
-                    mEngineOn = true;
-                    break;
-                case GPS_STATUS_SESSION_END:
-                    mNavigating = false;
-                    break;
-                case GPS_STATUS_ENGINE_ON:
-                    mEngineOn = true;
-                    break;
-                case GPS_STATUS_ENGINE_OFF:
-                    mEngineOn = false;
-                    mNavigating = false;
-                    break;
-            }
+        if (wasNavigating != mNavigating) {
+            mListenerHelper.onStatusChanged(mNavigating);
 
-            if (wasNavigating != mNavigating) {
-                int size = mListeners.size();
-                for (int i = 0; i < size; i++) {
-                    Listener listener = mListeners.get(i);
-                    try {
-                        if (mNavigating) {
-                            listener.mListener.onGpsStarted();
-                        } else {
-                            listener.mListener.onGpsStopped();
-                        }
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "RemoteException in reportStatus");
-                        mListeners.remove(listener);
-                        // adjust for size of list changing
-                        size--;
-                    }
-                }
-
-                // send an intent to notify that the GPS has been enabled or disabled.
-                Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
-                intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating);
-                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-            }
+            // send an intent to notify that the GPS has been enabled or disabled
+            Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
+            intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
         }
     }
 
@@ -1282,25 +1219,16 @@
      * called from native code to update SV info
      */
     private void reportSvStatus() {
-
         int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks);
-
-        synchronized (mListeners) {
-            int size = mListeners.size();
-            for (int i = 0; i < size; i++) {
-                Listener listener = mListeners.get(i);
-                try {
-                    listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs,
-                            mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK],
-                            mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "RemoteException in reportSvInfo");
-                    mListeners.remove(listener);
-                    // adjust for size of list changing
-                    size--;
-                }
-            }
-        }
+        mListenerHelper.onSvStatusChanged(
+                svCount,
+                mSvs,
+                mSnrs,
+                mSvElevations,
+                mSvAzimuths,
+                mSvMasks[EPHEMERIS_MASK],
+                mSvMasks[ALMANAC_MASK],
+                mSvMasks[USED_FOR_FIX_MASK]);
 
         if (VERBOSE) {
             Log.v(TAG, "SV count: " + svCount +
@@ -1398,26 +1326,16 @@
      * called from native code to report NMEA data received
      */
     private void reportNmea(long timestamp) {
-        synchronized (mListeners) {
-            int size = mListeners.size();
-            if (size > 0) {
-                // don't bother creating the String if we have no listeners
-                int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
-                String nmea = new String(mNmeaBuffer, 0, length);
+        int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
+        String nmea = new String(mNmeaBuffer, 0 /* offset */, length);
+        mListenerHelper.onNmeaReceived(timestamp, nmea);
+    }
 
-                for (int i = 0; i < size; i++) {
-                    Listener listener = mListeners.get(i);
-                    try {
-                        listener.mListener.onNmeaReceived(timestamp, nmea);
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "RemoteException in reportNmea");
-                        mListeners.remove(listener);
-                        // adjust for size of list changing
-                        size--;
-                    }
-                }
-            }
-        }
+    /**
+     * called from native code - Gps Data callback
+     */
+    private void reportMeasurementData(GpsMeasurementsEvent event) {
+        mGpsMeasurementsProvider.onMeasurementsAvailable(event);
     }
 
     /**
@@ -1845,7 +1763,7 @@
                 Log.e(TAG, "No APN found to select.");
             }
         } catch (Exception e) {
-            Log.e(TAG, "Error encountered on selectiong the APN.", e);
+            Log.e(TAG, "Error encountered on selecting the APN.", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -2008,4 +1926,9 @@
     private static native boolean native_remove_geofence(int geofenceId);
     private static native boolean native_resume_geofence(int geofenceId, int transitions);
     private static native boolean native_pause_geofence(int geofenceId);
+
+    // Gps Hal measurements support.
+    private static native boolean native_is_measurement_supported();
+    private static native boolean native_start_measurement_collection();
+    private static native boolean native_stop_measurement_collection();
 }
diff --git a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java
new file mode 100644
index 0000000..001f638
--- /dev/null
+++ b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.location.GpsMeasurementsEvent;
+import android.location.IGpsMeasurementsListener;
+import android.os.RemoteException;
+
+/**
+ * An base implementation for GPS measurements provider.
+ * It abstracts out the responsibility of handling listeners, while still allowing technology
+ * specific implementations to be built.
+ *
+ * @hide
+ */
+public abstract class GpsMeasurementsProvider
+        extends RemoteListenerHelper<IGpsMeasurementsListener> {
+
+    public void onMeasurementsAvailable(final GpsMeasurementsEvent event) {
+        ListenerOperation<IGpsMeasurementsListener> operation =
+                new ListenerOperation<IGpsMeasurementsListener>() {
+            @Override
+            public void execute(IGpsMeasurementsListener listener) throws RemoteException {
+                listener.onGpsMeasurementsReceived(event);
+            }
+        };
+
+        foreach(operation);
+    }
+}
diff --git a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java
new file mode 100644
index 0000000..b741e75
--- /dev/null
+++ b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.location.IGpsStatusListener;
+import android.os.RemoteException;
+
+/**
+ * Implementation of a handler for {@link IGpsStatusListener}.
+ */
+abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusListener> {
+    public void onFirstFix(final int timeToFirstFix) {
+        Operation operation = new Operation() {
+            @Override
+            public void execute(IGpsStatusListener listener) throws RemoteException {
+                listener.onFirstFix(timeToFirstFix);
+            }
+        };
+
+        foreach(operation);
+    }
+
+    public void onStatusChanged(final boolean isNavigating) {
+        Operation operation = new Operation() {
+            @Override
+            public void execute(IGpsStatusListener listener) throws RemoteException {
+                if (isNavigating) {
+                    listener.onGpsStarted();
+                } else {
+                    listener.onGpsStopped();
+                }
+            }
+        };
+
+        foreach(operation);
+    }
+
+    public void onSvStatusChanged(
+            final int svCount,
+            final int[] prns,
+            final float[] snrs,
+            final float[] elevations,
+            final float[] azimuths,
+            final int ephemerisMask,
+            final int almanacMask,
+            final int usedInFixMask) {
+        Operation operation = new Operation() {
+            @Override
+            public void execute(IGpsStatusListener listener) throws RemoteException {
+                listener.onSvStatusChanged(
+                        svCount,
+                        prns,
+                        snrs,
+                        elevations,
+                        azimuths,
+                        ephemerisMask,
+                        almanacMask,
+                        usedInFixMask);
+            }
+        };
+
+        foreach(operation);
+    }
+
+    public void onNmeaReceived(final long timestamp, final String nmea) {
+        Operation operation = new Operation() {
+            @Override
+            public void execute(IGpsStatusListener listener) throws RemoteException {
+                listener.onNmeaReceived(timestamp, nmea);
+            }
+        };
+
+        foreach(operation);
+    }
+
+    private abstract class Operation implements ListenerOperation<IGpsStatusListener> { }
+}
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
new file mode 100644
index 0000000..79335f7
--- /dev/null
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+/**
+ * A helper class, that handles operations in remote listeners, and tracks for remote process death.
+ */
+abstract class RemoteListenerHelper<TListener extends IInterface> {
+    private static final String TAG = "RemoteListenerHelper";
+
+    private final HashMap<IBinder, LinkedListener> mListenerMap =
+            new HashMap<IBinder, LinkedListener>();
+
+    public boolean addListener(@NonNull TListener listener) {
+        Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener.");
+
+        if (!isSupported()) {
+            Log.e(TAG, "Refused to add listener, the feature is not supported.");
+            return false;
+        }
+
+        IBinder binder = listener.asBinder();
+        LinkedListener deathListener = new LinkedListener(listener);
+        synchronized (mListenerMap) {
+            if (mListenerMap.containsKey(binder)) {
+                // listener already added
+                return true;
+            }
+
+            try {
+                binder.linkToDeath(deathListener, 0 /* flags */);
+            } catch (RemoteException e) {
+                // if the remote process registering the listener is already death, just swallow the
+                // exception and continue
+                Log.e(TAG, "Remote listener already died.", e);
+                return false;
+            }
+
+            mListenerMap.put(binder, deathListener);
+            if (mListenerMap.size() == 1) {
+                onFirstListenerAdded();
+            }
+        }
+
+        return true;
+    }
+
+    public boolean removeListener(@NonNull TListener listener) {
+        Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener.");
+
+        if (!isSupported()) {
+            Log.e(TAG, "Refused to remove listener, the feature is not supported.");
+            return false;
+        }
+
+        IBinder binder = listener.asBinder();
+        LinkedListener linkedListener;
+        synchronized (mListenerMap) {
+            linkedListener = mListenerMap.remove(binder);
+            if (mListenerMap.isEmpty() && linkedListener != null) {
+                onLastListenerRemoved();
+            }
+        }
+
+        if (linkedListener != null) {
+            binder.unlinkToDeath(linkedListener, 0 /* flags */);
+        }
+
+        return true;
+    }
+
+    protected abstract boolean isSupported();
+
+    protected void onFirstListenerAdded() {
+        // event triggered when the first listener has been added
+    }
+
+    protected void onLastListenerRemoved() {
+        // event triggered when the last listener has bee removed
+    }
+
+    protected interface ListenerOperation<TListener extends IInterface> {
+        void execute(TListener listener) throws RemoteException;
+    }
+
+    protected void foreach(ListenerOperation operation) {
+        Collection<LinkedListener> linkedListeners;
+        synchronized (mListenerMap) {
+            Collection<LinkedListener> values = mListenerMap.values();
+            linkedListeners = new ArrayList<LinkedListener>(values);
+        }
+
+        for (LinkedListener linkedListener : linkedListeners) {
+            TListener listener = linkedListener.getUnderlyingListener();
+            try {
+                operation.execute(listener);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error in monitored listener.", e);
+                removeListener(listener);
+            }
+        }
+    }
+
+    private class LinkedListener implements IBinder.DeathRecipient {
+        private final TListener mListener;
+
+        public LinkedListener(@NonNull TListener listener) {
+            mListener = listener;
+        }
+
+        @NonNull
+        public TListener getUnderlyingListener() {
+            return mListener;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.d(TAG, "Remote Listener died: " + mListener);
+            removeListener(mListener);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 871c94f..c19951f 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -52,13 +52,11 @@
     /** Sentinel value returned when public key is not found. */
     protected static final long PUBLIC_KEY_NOT_FOUND = -1;
 
-    private final Object mLockObject = new Object();
-
     private final LongSparseArray<KeySet> mKeySets;
 
     private final LongSparseArray<PublicKey> mPublicKeys;
 
-    protected final LongSparseArray<Set<Long>> mKeySetMapping;
+    protected final LongSparseArray<ArraySet<Long>> mKeySetMapping;
 
     private final Map<String, PackageSetting> mPackages;
 
@@ -69,7 +67,7 @@
     public KeySetManagerService(Map<String, PackageSetting> packages) {
         mKeySets = new LongSparseArray<KeySet>();
         mPublicKeys = new LongSparseArray<PublicKey>();
-        mKeySetMapping = new LongSparseArray<Set<Long>>();
+        mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
         mPackages = packages;
     }
 
@@ -84,18 +82,16 @@
      *
      * Note that this can return true for multiple KeySets.
      */
-    public boolean packageIsSignedBy(String packageName, KeySet ks) {
-        synchronized (mLockObject) {
-            PackageSetting pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new NullPointerException("Invalid package name");
-            }
-            if (pkg.keySetData == null) {
-                throw new NullPointerException("Package has no KeySet data");
-            }
-            long id = getIdByKeySetLocked(ks);
-            return pkg.keySetData.packageIsSignedBy(id);
+    public boolean packageIsSignedByLPr(String packageName, KeySet ks) {
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("Invalid package name");
         }
+        if (pkg.keySetData == null) {
+            throw new NullPointerException("Package has no KeySet data");
+        }
+        long id = getIdByKeySetLPr(ks);
+        return pkg.keySetData.packageIsSignedBy(id);
     }
 
     /**
@@ -103,22 +99,20 @@
      * in its manifest that a) contains the given keys and b) is named
      * alias by that package.
      */
-    public void addDefinedKeySetToPackage(String packageName,
+    public void addDefinedKeySetToPackageLPw(String packageName,
             Set<PublicKey> keys, String alias) {
         if ((packageName == null) || (keys == null) || (alias == null)) {
             Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
             return;
         }
-        synchronized (mLockObject) {
-            PackageSetting pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            // Add to KeySets, then to package
-            KeySet ks = addKeySetLocked(keys);
-            long id = getIdByKeySetLocked(ks);
-            pkg.keySetData.addDefinedKeySet(id, alias);
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("Unknown package");
         }
+        // Add to KeySets, then to package
+        KeySet ks = addKeySetLPw(keys);
+        long id = getIdByKeySetLPr(ks);
+        pkg.keySetData.addDefinedKeySet(id, alias);
     }
 
     /**
@@ -126,53 +120,49 @@
      * alias in its manifest to be an upgradeKeySet.  This must be called
      * after all of the defined KeySets have been added.
      */
-    public void addUpgradeKeySetToPackage(String packageName, String alias) {
+    public void addUpgradeKeySetToPackageLPw(String packageName, String alias) {
         if ((packageName == null) || (alias == null)) {
             Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
             return;
         }
-        synchronized (mLockObject) {
-            PackageSetting pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            pkg.keySetData.addUpgradeKeySet(alias);
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("Unknown package");
         }
+        pkg.keySetData.addUpgradeKeySet(alias);
     }
 
     /**
      * Similar to the above, this informs the system that the given package
      * was signed by the provided KeySet.
      */
-    public void addSigningKeySetToPackage(String packageName,
+    public void addSigningKeySetToPackageLPw(String packageName,
             Set<PublicKey> signingKeys) {
         if ((packageName == null) || (signingKeys == null)) {
             Slog.w(TAG, "Got null argument for a signing keyset, ignoring!");
             return;
         }
-        synchronized (mLockObject) {
-            // add the signing KeySet
-            KeySet ks = addKeySetLocked(signingKeys);
-            long id = getIdByKeySetLocked(ks);
-            Set<Long> publicKeyIds = mKeySetMapping.get(id);
-            if (publicKeyIds == null) {
-                throw new NullPointerException("Got invalid KeySet id");
-            }
+        // add the signing KeySet
+        KeySet ks = addKeySetLPw(signingKeys);
+        long id = getIdByKeySetLPr(ks);
+        Set<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) {
-                throw new NullPointerException("No such package!");
-            }
-            pkg.keySetData.setProperSigningKeySet(id);
-            // for each KeySet which is a subset of the one above, add the
-            // 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);
-                if (publicKeyIds.containsAll(definedKeys)) {
-                    pkg.keySetData.addSigningKeySet(keySetID);
-                }
+        // attach it to the package
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("No such package!");
+        }
+        pkg.keySetData.setProperSigningKeySet(id);
+        // for each KeySet which is a subset of the one above, add the
+        // 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);
+            if (publicKeyIds.containsAll(definedKeys)) {
+                pkg.keySetData.addSigningKeySet(keySetID);
             }
         }
     }
@@ -181,13 +171,7 @@
      * Fetches the stable identifier associated with the given KeySet. Returns
      * {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
      */
-    public long getIdByKeySet(KeySet ks) {
-        synchronized (mLockObject) {
-            return getIdByKeySetLocked(ks);
-        }
-    }
-
-    private long getIdByKeySetLocked(KeySet ks) {
+    private long getIdByKeySetLPr(KeySet ks) {
         for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
             KeySet value = mKeySets.valueAt(keySetIndex);
             if (ks.equals(value)) {
@@ -203,10 +187,8 @@
      * Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
      * identify a {@link KeySet}.
      */
-    public KeySet getKeySetById(long id) {
-        synchronized (mLockObject) {
-            return mKeySets.get(id);
-        }
+    public KeySet getKeySetByIdLPr(long id) {
+        return mKeySets.get(id);
     }
 
     /**
@@ -215,18 +197,16 @@
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public KeySet getKeySetByAliasAndPackageName(String packageName, String alias) {
-        synchronized (mLockObject) {
-            PackageSetting p = mPackages.get(packageName);
-            if (p == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            if (p.keySetData == null) {
-                throw new IllegalArgumentException("Package has no keySet data");
-            }
-            long keySetId = p.keySetData.getAliases().get(alias);
-            return mKeySets.get(keySetId);
+    public KeySet getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
+        PackageSetting p = mPackages.get(packageName);
+        if (p == null) {
+            throw new NullPointerException("Unknown package");
         }
+        if (p.keySetData == null) {
+            throw new IllegalArgumentException("Package has no keySet data");
+        }
+        long keySetId = p.keySetData.getAliases().get(alias);
+        return mKeySets.get(keySetId);
     }
 
     /**
@@ -236,17 +216,15 @@
      * Returns {@code null} if the identifier doesn't
      * identify a {@link KeySet}.
      */
-    public ArraySet<PublicKey> getPublicKeysFromKeySet(long id) {
-        synchronized (mLockObject) {
-            if(mKeySetMapping.get(id) == null) {
-                return null;
-            }
-            ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
-            for (long pkId : mKeySetMapping.get(id)) {
-                mPubKeys.add(mPublicKeys.get(pkId));
-            }
-            return mPubKeys;
+    public ArraySet<PublicKey> getPublicKeysFromKeySetLPr(long id) {
+        if(mKeySetMapping.get(id) == null) {
+            return null;
         }
+        ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
+        for (long pkId : mKeySetMapping.get(id)) {
+            mPubKeys.add(mPublicKeys.get(pkId));
+        }
+        return mPubKeys;
     }
 
     /**
@@ -256,21 +234,19 @@
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public Set<KeySet> getSigningKeySetsByPackageName(String packageName) {
-        synchronized (mLockObject) {
-            Set<KeySet> signingKeySets = new ArraySet<KeySet>();
-            PackageSetting p = mPackages.get(packageName);
-            if (p == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            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;
+    public Set<KeySet> getSigningKeySetsByPackageNameLPr(String packageName) {
+        Set<KeySet> signingKeySets = new ArraySet<KeySet>();
+        PackageSetting p = mPackages.get(packageName);
+        if (p == null) {
+            throw new NullPointerException("Unknown package");
         }
+        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;
     }
 
     /**
@@ -280,23 +256,21 @@
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public ArraySet<KeySet> getUpgradeKeySetsByPackageName(String packageName) {
-        synchronized (mLockObject) {
-            ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
-            PackageSetting p = mPackages.get(packageName);
-            if (p == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            if (p.keySetData == null) {
-                throw new IllegalArgumentException("Package has no keySet data");
-            }
-            if (p.keySetData.isUsingUpgradeKeySets()) {
-                for (long l : p.keySetData.getUpgradeKeySets()) {
-                    upgradeKeySets.add(mKeySets.get(l));
-                }
-            }
-            return upgradeKeySets;
+    public ArraySet<KeySet> getUpgradeKeySetsByPackageNameLPr(String packageName) {
+        ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
+        PackageSetting p = mPackages.get(packageName);
+        if (p == null) {
+            throw new NullPointerException("Unknown package");
         }
+        if (p.keySetData == null) {
+            throw new IllegalArgumentException("Package has no keySet data");
+        }
+        if (p.keySetData.isUsingUpgradeKeySets()) {
+            for (long l : p.keySetData.getUpgradeKeySets()) {
+                upgradeKeySets.add(mKeySets.get(l));
+            }
+        }
+        return upgradeKeySets;
     }
 
     /**
@@ -313,19 +287,19 @@
      *
      * Throws if the provided set is {@code null}.
      */
-    private KeySet addKeySetLocked(Set<PublicKey> keys) {
+    private KeySet addKeySetLPw(Set<PublicKey> keys) {
         if (keys == null) {
             throw new NullPointerException("Provided keys cannot be null");
         }
         // add each of the keys in the provided set
-        Set<Long> addedKeyIds = new ArraySet<Long>(keys.size());
+        ArraySet<Long> addedKeyIds = new ArraySet<Long>(keys.size());
         for (PublicKey k : keys) {
-            long id = addPublicKeyLocked(k);
+            long id = addPublicKeyLPw(k);
             addedKeyIds.add(id);
         }
 
         // check to see if the resulting keyset is new
-        long existingKeySetId = getIdFromKeyIdsLocked(addedKeyIds);
+        long existingKeySetId = getIdFromKeyIdsLPr(addedKeyIds);
         if (existingKeySetId != KEYSET_NOT_FOUND) {
             return mKeySets.get(existingKeySetId);
         }
@@ -333,7 +307,7 @@
         // create the KeySet object
         KeySet ks = new KeySet(new Binder());
         // get the first unoccupied slot in mKeySets
-        long id = getFreeKeySetIDLocked();
+        long id = getFreeKeySetIDLPw();
         // add the KeySet object to it
         mKeySets.put(id, ks);
         // add the stable key ids to the mapping
@@ -358,14 +332,14 @@
     /**
      * Adds the given PublicKey to the system, deduping as it goes.
      */
-    private long addPublicKeyLocked(PublicKey key) {
+    private long addPublicKeyLPw(PublicKey key) {
         // check if the public key is new
-        long existingKeyId = getIdForPublicKeyLocked(key);
+        long existingKeyId = getIdForPublicKeyLPr(key);
         if (existingKeyId != PUBLIC_KEY_NOT_FOUND) {
             return existingKeyId;
         }
         // if it's new find the first unoccupied slot in the public keys
-        long id = getFreePublicKeyIdLocked();
+        long id = getFreePublicKeyIdLPw();
         // add the public key to it
         mPublicKeys.put(id, key);
         // return the stable identifier
@@ -377,7 +351,7 @@
      *
      * Returns KEYSET_NOT_FOUND if there isn't one.
      */
-    private long getIdFromKeyIdsLocked(Set<Long> publicKeyIds) {
+    private long getIdFromKeyIdsLPr(Set<Long> publicKeyIds) {
         for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
             Set<Long> value = mKeySetMapping.valueAt(keyMapIndex);
             if (value.equals(publicKeyIds)) {
@@ -390,16 +364,7 @@
     /**
      * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
      */
-    protected long getIdForPublicKey(PublicKey k) {
-        synchronized (mLockObject) {
-            return getIdForPublicKeyLocked(k);
-        }
-    }
-
-    /**
-     * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
-     */
-    private long getIdForPublicKeyLocked(PublicKey k) {
+    private long getIdForPublicKeyLPr(PublicKey k) {
         String encodedPublicKey = new String(k.getEncoded());
         for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) {
             PublicKey value = mPublicKeys.valueAt(publicKeyIndex);
@@ -414,7 +379,7 @@
     /**
      * Gets an unused stable identifier for a KeySet.
      */
-    private long getFreeKeySetIDLocked() {
+    private long getFreeKeySetIDLPw() {
         lastIssuedKeySetId += 1;
         return lastIssuedKeySetId;
     }
@@ -422,72 +387,69 @@
     /**
      * Same as above, but for public keys.
      */
-    private long getFreePublicKeyIdLocked() {
+    private long getFreePublicKeyIdLPw() {
         lastIssuedKeyId += 1;
         return lastIssuedKeyId;
     }
 
-    public void removeAppKeySetData(String packageName) {
-        synchronized (mLockObject) {
-            // Get the package's known keys and KeySets
-            Set<Long> deletableKeySets = getOriginalKeySetsByPackageNameLocked(packageName);
-            Set<Long> deletableKeys = new ArraySet<Long>();
-            Set<Long> knownKeys = null;
-            for (Long ks : deletableKeySets) {
+    public void removeAppKeySetDataLPw(String packageName) {
+        // Get the package's known keys and KeySets
+        ArraySet<Long> deletableKeySets = getOriginalKeySetsByPackageNameLPr(packageName);
+        ArraySet<Long> deletableKeys = new ArraySet<Long>();
+        ArraySet<Long> knownKeys = null;
+        for (Long ks : deletableKeySets) {
+            knownKeys = mKeySetMapping.get(ks);
+            if (knownKeys != null) {
+                deletableKeys.addAll(knownKeys);
+            }
+        }
+
+        // Now remove the keys and KeySets on which any other package relies
+        for (String pkgName : mPackages.keySet()) {
+            if (pkgName.equals(packageName)) {
+                continue;
+            }
+            ArraySet<Long> knownKeySets = getOriginalKeySetsByPackageNameLPr(pkgName);
+            deletableKeySets.removeAll(knownKeySets);
+            knownKeys = new ArraySet<Long>();
+            for (Long ks : knownKeySets) {
                 knownKeys = mKeySetMapping.get(ks);
                 if (knownKeys != null) {
-                    deletableKeys.addAll(knownKeys);
+                    deletableKeys.removeAll(knownKeys);
                 }
             }
-
-            // Now remove the keys and KeySets on which any other package relies
-            for (String pkgName : mPackages.keySet()) {
-                if (pkgName.equals(packageName)) {
-                    continue;
-                }
-                Set<Long> knownKeySets = getOriginalKeySetsByPackageNameLocked(pkgName);
-                deletableKeySets.removeAll(knownKeySets);
-                knownKeys = new ArraySet<Long>();
-                for (Long ks : knownKeySets) {
-                    knownKeys = mKeySetMapping.get(ks);
-                    if (knownKeys != null) {
-                        deletableKeys.removeAll(knownKeys);
-                    }
-                }
-            }
-
-            // The remaining keys and KeySets are not relied on by any other
-            // application and so can be safely deleted.
-            for (Long ks : deletableKeySets) {
-                mKeySets.delete(ks);
-                mKeySetMapping.delete(ks);
-            }
-            for (Long keyId : deletableKeys) {
-                mPublicKeys.delete(keyId);
-            }
-
-            // Now remove the deleted KeySets from each package's signingKeySets
-            for (String pkgName : mPackages.keySet()) {
-                PackageSetting p = mPackages.get(pkgName);
-                for (Long ks : deletableKeySets) {
-                    p.keySetData.removeSigningKeySet(ks);
-                }
-            }
-
-            // Finally, remove all KeySets from the original package
-            PackageSetting p = mPackages.get(packageName);
-            clearPackageKeySetDataLocked(p);
         }
+
+        // The remaining keys and KeySets are not relied on by any other
+        // application and so can be safely deleted.
+        for (Long ks : deletableKeySets) {
+            mKeySets.delete(ks);
+            mKeySetMapping.delete(ks);
+        }
+        for (Long keyId : deletableKeys) {
+            mPublicKeys.delete(keyId);
+        }
+
+        // Now remove the deleted KeySets from each package's signingKeySets
+        for (String pkgName : mPackages.keySet()) {
+            PackageSetting p = mPackages.get(pkgName);
+            for (Long ks : deletableKeySets) {
+                p.keySetData.removeSigningKeySet(ks);
+            }
+        }
+        // Finally, remove all KeySets from the original package
+        PackageSetting p = mPackages.get(packageName);
+        clearPackageKeySetDataLPw(p);
     }
 
-    private void clearPackageKeySetDataLocked(PackageSetting p) {
+    private void clearPackageKeySetDataLPw(PackageSetting p) {
         p.keySetData.removeAllSigningKeySets();
         p.keySetData.removeAllUpgradeKeySets();
         p.keySetData.removeAllDefinedKeySets();
         return;
     }
 
-    private Set<Long> getOriginalKeySetsByPackageNameLocked(String packageName) {
+    private ArraySet<Long> getOriginalKeySetsByPackageNameLPr(String packageName) {
         PackageSetting p = mPackages.get(packageName);
         if (p == null) {
             throw new NullPointerException("Unknown package");
@@ -495,7 +457,7 @@
         if (p.keySetData == null) {
             throw new IllegalArgumentException("Package has no keySet data");
         }
-        Set<Long> knownKeySets = new ArraySet<Long>();
+        ArraySet<Long> knownKeySets = new ArraySet<Long>();
         knownKeySets.add(p.keySetData.getProperSigningKeySet());
         if (p.keySetData.isUsingDefinedKeySets()) {
             for (long ks : p.keySetData.getDefinedKeySets()) {
@@ -509,82 +471,80 @@
         return new String(Base64.encode(k.getEncoded(), 0));
     }
 
-    public void dump(PrintWriter pw, String packageName,
-            PackageManagerService.DumpState dumpState) {
-        synchronized (mLockObject) {
-            boolean printedHeader = false;
-            for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
-                String keySetPackage = e.getKey();
-                if (packageName != null && !packageName.equals(keySetPackage)) {
-                    continue;
+    public void dumpLPr(PrintWriter pw, String packageName,
+                        PackageManagerService.DumpState dumpState) {
+        boolean printedHeader = false;
+        for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
+            String keySetPackage = e.getKey();
+            if (packageName != null && !packageName.equals(keySetPackage)) {
+                continue;
+            }
+            if (!printedHeader) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                pw.println("Key Set Manager:");
+                printedHeader = true;
+            }
+            PackageSetting pkg = e.getValue();
+            pw.print("  ["); pw.print(keySetPackage); pw.println("]");
+            if (pkg.keySetData != null) {
+                boolean printedLabel = false;
+                for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+                    if (!printedLabel) {
+                        pw.print("      KeySets Aliases: ");
+                        printedLabel = true;
+                    } else {
+                        pw.print(", ");
+                    }
+                    pw.print(entry.getKey());
+                    pw.print('=');
+                    pw.print(Long.toString(entry.getValue()));
                 }
-                if (!printedHeader) {
-                    if (dumpState.onTitlePrinted())
-                        pw.println();
-                    pw.println("Key Set Manager:");
-                    printedHeader = true;
+                if (printedLabel) {
+                    pw.println("");
                 }
-                PackageSetting pkg = e.getValue();
-                pw.print("  ["); pw.print(keySetPackage); pw.println("]");
-                if (pkg.keySetData != null) {
-                    boolean printedLabel = false;
-                    for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+                printedLabel = false;
+                if (pkg.keySetData.isUsingDefinedKeySets()) {
+                    for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
                         if (!printedLabel) {
-                            pw.print("      KeySets Aliases: ");
-                            printedLabel = true;
-                        } else {
-                            pw.print(", ");
-                        }
-                        pw.print(entry.getKey());
-                        pw.print('=');
-                        pw.print(Long.toString(entry.getValue()));
-                    }
-                    if (printedLabel) {
-                        pw.println("");
-                    }
-                    printedLabel = false;
-                    if (pkg.keySetData.isUsingDefinedKeySets()) {
-                        for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
-                            if (!printedLabel) {
-                                pw.print("      Defined KeySets: ");
-                                printedLabel = true;
-                            } else {
-                                pw.print(", ");
-                            }
-                            pw.print(Long.toString(keySetId));
-                        }
-                    }
-                    if (printedLabel) {
-                        pw.println("");
-                    }
-                    printedLabel = false;
-                    for (long keySetId : pkg.keySetData.getSigningKeySets()) {
-                        if (!printedLabel) {
-                            pw.print("      Signing KeySets: ");
+                            pw.print("      Defined KeySets: ");
                             printedLabel = true;
                         } else {
                             pw.print(", ");
                         }
                         pw.print(Long.toString(keySetId));
                     }
-                    if (printedLabel) {
-                        pw.println("");
+                }
+                if (printedLabel) {
+                    pw.println("");
+                }
+                printedLabel = false;
+                for (long keySetId : pkg.keySetData.getSigningKeySets()) {
+                    if (!printedLabel) {
+                        pw.print("      Signing KeySets: ");
+                        printedLabel = true;
+                    } else {
+                        pw.print(", ");
                     }
-                    printedLabel = false;
-                    if (pkg.keySetData.isUsingUpgradeKeySets()) {
-                        for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
-                            if (!printedLabel) {
-                                pw.print("      Upgrade KeySets: ");
-                                printedLabel = true;
-                            } else {
-                                pw.print(", ");
-                            }
-                            pw.print(Long.toString(keySetId));
+                    pw.print(Long.toString(keySetId));
+                }
+                if (printedLabel) {
+                    pw.println("");
+                }
+                printedLabel = false;
+                if (pkg.keySetData.isUsingUpgradeKeySets()) {
+                    for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
+                        if (!printedLabel) {
+                            pw.print("      Upgrade KeySets: ");
+                            printedLabel = true;
+                        } else {
+                            pw.print(", ");
                         }
+                        pw.print(Long.toString(keySetId));
                     }
-                    if (printedLabel) {
-                        pw.println("");
-                    }
+                }
+                if (printedLabel) {
+                    pw.println("");
                 }
             }
         }
@@ -651,7 +611,7 @@
             // The KeySet information read previously from packages.xml is invalid.
             // Destroy it all.
             for (PackageSetting p : mPackages.values()) {
-                clearPackageKeySetDataLocked(p);
+                clearPackageKeySetDataLPw(p);
             }
             return;
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 682a38d6..0eb922d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -24,29 +24,36 @@
 import android.content.Context;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageInstallerObserver;
 import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.HandlerThread;
 import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ArraySet;
+import android.util.ExceptionUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
 import com.google.android.collect.Sets;
 
 import java.io.File;
 import java.io.FilenameFilter;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 public class PackageInstallerService extends IPackageInstaller.Stub {
     private static final String TAG = "PackageInstaller";
@@ -68,6 +75,8 @@
     @GuardedBy("mSessions")
     private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
 
+    private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>();
+
     private static final FilenameFilter sStageFilter = new FilenameFilter() {
         @Override
         public boolean accept(File dir, String name) {
@@ -138,10 +147,10 @@
     }
 
     @Override
-    public int createSession(String installerPackageName, PackageInstallerParams params,
+    public int createSession(String installerPackageName, InstallSessionParams params,
             int userId) {
         final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+        mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession");
 
         if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
                 UserManager.DISALLOW_INSTALL_APPS)) {
@@ -158,19 +167,32 @@
             params.installFlags |= INSTALL_REPLACE_EXISTING;
         }
 
+        // Sanity check that install could fit
+        if (params.deltaSize > 0) {
+            try {
+                mPm.freeStorage(params.deltaSize);
+            } catch (IOException e) {
+                throw ExceptionUtils.wrap(e);
+            }
+        }
+
+        final int sessionId;
+        final PackageInstallerSession session;
         synchronized (mSessions) {
+            sessionId = allocateSessionIdLocked();
+
             final long createdMillis = System.currentTimeMillis();
-            final int sessionId = allocateSessionIdLocked();
             final File sessionStageDir = prepareSessionStageDir(sessionId);
 
-            final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
-                    sessionId, userId, installerPackageName, callingUid, params, createdMillis,
-                    sessionStageDir, mInstallThread.getLooper());
+            session = new PackageInstallerSession(mCallback, mPm, sessionId, userId,
+                    installerPackageName, callingUid, params, createdMillis, sessionStageDir,
+                    mInstallThread.getLooper());
             mSessions.put(sessionId, session);
-
-            writeSessionsAsync();
-            return sessionId;
         }
+
+        notifySessionCreated(session.generateInfo());
+        writeSessionsAsync();
+        return sessionId;
     }
 
     @Override
@@ -217,44 +239,130 @@
     }
 
     @Override
-    public int[] getSessions(String installerPackageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
-        mAppOps.checkPackage(callingUid, installerPackageName);
+    public List<InstallSessionInfo> getSessions(int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getSessions");
 
-        int[] matching = new int[0];
+        final List<InstallSessionInfo> result = new ArrayList<>();
         synchronized (mSessions) {
             for (int i = 0; i < mSessions.size(); i++) {
-                final int key = mSessions.keyAt(i);
                 final PackageInstallerSession session = mSessions.valueAt(i);
-                if (session.userId == userId
-                        && session.installerPackageName.equals(installerPackageName)) {
-                    matching = ArrayUtils.appendInt(matching, key);
+                if (session.userId == userId) {
+                    result.add(session.generateInfo());
                 }
             }
         }
-        return matching;
+        return result;
     }
 
     @Override
-    public void uninstall(String basePackageName, int flags, IPackageDeleteObserver observer,
+    public void uninstall(String packageName, int flags, IPackageDeleteObserver observer,
             int userId) {
-        mPm.deletePackageAsUser(basePackageName, observer, userId, flags);
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
+        mPm.deletePackageAsUser(packageName, observer, userId, flags);
     }
 
     @Override
     public void uninstallSplit(String basePackageName, String overlayName, int flags,
             IPackageDeleteObserver observer, int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit");
+
         // TODO: flesh out once PM has split support
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void registerObserver(IPackageInstallerObserver observer, int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerObserver");
+
+        // TODO: consider restricting to active launcher app only
+        mObservers.register(observer, new UserHandle(userId));
+    }
+
+    @Override
+    public void unregisterObserver(IPackageInstallerObserver observer, int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "unregisterObserver");
+        mObservers.unregister(observer);
+    }
+
+    private int getSessionUserId(int sessionId) {
+        synchronized (mSessions) {
+            return UserHandle.getUserId(mSessions.get(sessionId).installerUid);
+        }
+    }
+
+    private void notifySessionCreated(InstallSessionInfo info) {
+        final int userId = getSessionUserId(info.sessionId);
+        final int n = mObservers.beginBroadcast();
+        for (int i = 0; i < n; i++) {
+            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
+            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+            if (userId == user.getIdentifier()) {
+                try {
+                    observer.onSessionCreated(info);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    private void notifySessionProgress(int sessionId, int progress) {
+        final int userId = getSessionUserId(sessionId);
+        final int n = mObservers.beginBroadcast();
+        for (int i = 0; i < n; i++) {
+            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
+            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+            if (userId == user.getIdentifier()) {
+                try {
+                    observer.onSessionProgress(sessionId, progress);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    private void notifySessionFinished(int sessionId, boolean success) {
+        final int userId = getSessionUserId(sessionId);
+        final int n = mObservers.beginBroadcast();
+        for (int i = 0; i < n; i++) {
+            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
+            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+            if (userId == user.getIdentifier()) {
+                try {
+                    observer.onSessionFinished(sessionId, success);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("Active install sessions:");
+        pw.increaseIndent();
+        synchronized (mSessions) {
+            final int N = mSessions.size();
+            for (int i = 0; i < N; i++) {
+                final PackageInstallerSession session = mSessions.valueAt(i);
+                session.dump(pw);
+                pw.println();
+            }
+        }
+        pw.println();
+        pw.decreaseIndent();
+    }
+
     class Callback {
-        public void onProgressChanged(PackageInstallerSession session) {
-            // TODO: notify listeners
+        public void onSessionProgress(PackageInstallerSession session, int progress) {
+            notifySessionProgress(session.sessionId, progress);
         }
 
-        public void onSessionInvalid(PackageInstallerSession session) {
+        public void onSessionFinished(PackageInstallerSession session, boolean success) {
+            notifySessionFinished(session.sessionId, success);
+            synchronized (mSessions) {
+                mSessions.remove(session.sessionId);
+            }
             writeSessionsAsync();
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3448803..c399fa2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -26,7 +26,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ApkLite;
@@ -47,9 +48,12 @@
 import android.system.OsConstants;
 import android.system.StructStat;
 import android.util.ArraySet;
+import android.util.ExceptionUtils;
+import android.util.MathUtils;
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 
 import libcore.io.Libcore;
@@ -75,7 +79,7 @@
     public final String installerPackageName;
     /** UID not persisted */
     public final int installerUid;
-    public final PackageInstallerParams params;
+    public final InstallSessionParams params;
     public final long createdMillis;
     public final File sessionStageDir;
 
@@ -91,13 +95,15 @@
 
                 try {
                     installLocked();
-                } catch (InstallFailedException e) {
+                } catch (PackageManagerException e) {
                     Slog.e(TAG, "Install failed: " + e);
-                    destroy();
+                    destroyInternal();
                     try {
-                        mRemoteObserver.packageInstalled(mPackageName, null, e.error);
+                        mRemoteObserver.packageInstalled(mPackageName, null, e.error,
+                                e.getMessage());
                     } catch (RemoteException ignored) {
                     }
+                    mCallback.onSessionFinished(PackageInstallerSession.this, false);
                 }
 
                 return true;
@@ -107,7 +113,8 @@
 
     private final Object mLock = new Object();
 
-    private int mProgress;
+    private int mClientProgress;
+    private int mProgress = 0;
 
     private String mPackageName;
     private int mVersionCode;
@@ -123,7 +130,7 @@
 
     public PackageInstallerSession(PackageInstallerService.Callback callback,
             PackageManagerService pm, int sessionId, int userId, String installerPackageName,
-            int installerUid, PackageInstallerParams params, long createdMillis, File sessionStageDir,
+            int installerUid, InstallSessionParams params, long createdMillis, File sessionStageDir,
             Looper looper) {
         mCallback = callback;
         mPm = pm;
@@ -152,14 +159,44 @@
         }
     }
 
+    public InstallSessionInfo generateInfo() {
+        final InstallSessionInfo info = new InstallSessionInfo();
+
+        info.sessionId = sessionId;
+        info.installerPackageName = installerPackageName;
+        info.progress = mProgress;
+
+        info.fullInstall = params.fullInstall;
+        info.packageName = params.packageName;
+        info.icon = params.icon;
+        info.title = params.title;
+
+        return info;
+    }
+
     @Override
-    public void updateProgress(int progress) {
-        mProgress = progress;
-        mCallback.onProgressChanged(this);
+    public void setClientProgress(int progress) {
+        mClientProgress = progress;
+        mProgress = MathUtils.constrain((mClientProgress * 8 * 100) / (params.progressMax * 10), 0, 80);
+        mCallback.onSessionProgress(this, mProgress);
+    }
+
+    @Override
+    public void addClientProgress(int progress) {
+        setClientProgress(mClientProgress + progress);
     }
 
     @Override
     public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+        try {
+            return openWriteInternal(name, offsetBytes, lengthBytes);
+        } catch (IOException e) {
+            throw ExceptionUtils.wrap(e);
+        }
+    }
+
+    private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
+            throws IOException {
         // TODO: relay over to DCS when installing to ASEC
 
         // Quick sanity check of state, and allocate a pipe for ourselves. We
@@ -206,9 +243,7 @@
             return new ParcelFileDescriptor(bridge.getClientSocket());
 
         } catch (ErrnoException e) {
-            throw new IllegalStateException("Failed to write", e);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to write", e);
+            throw e.rethrowAsIOException();
         }
     }
 
@@ -218,16 +253,16 @@
         mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
     }
 
-    private void installLocked() throws InstallFailedException {
+    private void installLocked() throws PackageManagerException {
         if (mInvalid) {
-            throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
+            throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
         }
 
         // Verify that all writers are hands-off
         if (mMutationsAllowed) {
             for (FileBridge bridge : mBridges) {
                 if (!bridge.isClosed()) {
-                    throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
+                    throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
                             "Files still open");
                 }
             }
@@ -257,6 +292,9 @@
             spliceExistingFilesIntoStage();
         }
 
+        // TODO: surface more granular state from dexopt
+        mCallback.onSessionProgress(this, 90);
+
         // TODO: for ASEC based applications, grow and stream in packages
 
         // We've reached point of no return; call into PMS to install the stage.
@@ -264,15 +302,20 @@
         final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
             @Override
-            public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
-                    throws RemoteException {
-                destroy();
-                remoteObserver.packageInstalled(basePackageName, extras, returnCode);
+            public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+                    String msg) {
+                destroyInternal();
+                try {
+                    remoteObserver.packageInstalled(basePackageName, extras, returnCode, msg);
+                } catch (RemoteException ignored) {
+                }
+                final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
+                mCallback.onSessionFinished(PackageInstallerSession.this, success);
             }
         };
 
-        mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, installerPackageName,
-                installerUid, new UserHandle(userId));
+        mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params,
+                installerPackageName, installerUid, new UserHandle(userId));
     }
 
     /**
@@ -281,13 +324,13 @@
      * <p>
      * Renames package files in stage to match split names defined inside.
      */
-    private void validateInstallLocked() throws InstallFailedException {
+    private void validateInstallLocked() throws PackageManagerException {
         mPackageName = null;
         mVersionCode = -1;
 
         final File[] files = sessionStageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
-            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
         }
 
         final ArraySet<String> seenSplits = new ArraySet<>();
@@ -298,12 +341,12 @@
             try {
                 info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES);
             } catch (PackageParserException e) {
-                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Failed to parse " + file + ": " + e);
             }
 
             if (!seenSplits.add(info.splitName)) {
-                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Split " + info.splitName + " was defined multiple times");
             }
 
@@ -327,7 +370,7 @@
                 name = "split_" + info.splitName + ".apk";
             }
             if (!FileUtils.isValidExtFilename(name)) {
-                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Invalid filename: " + name);
             }
             if (!file.getName().equals(name)) {
@@ -342,7 +385,7 @@
         if (params.fullInstall) {
             // Full installs must include a base package
             if (!seenSplits.contains(null)) {
-                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Full install must include a base package");
             }
 
@@ -350,7 +393,7 @@
             // Partial installs must be consistent with existing install.
             final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
             if (app == null) {
-                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Missing existing base package for " + mPackageName);
             }
 
@@ -359,7 +402,7 @@
                 info = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
                         PackageParser.PARSE_GET_SIGNATURES);
             } catch (PackageParserException e) {
-                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Failed to parse existing base " + app.getBaseCodePath() + ": " + e);
             }
 
@@ -369,18 +412,18 @@
     }
 
     private void assertPackageConsistent(String tag, String packageName, int versionCode,
-            Signature[] signatures) throws InstallFailedException {
+            Signature[] signatures) throws PackageManagerException {
         if (!mPackageName.equals(packageName)) {
-            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package "
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
                     + packageName + " inconsistent with " + mPackageName);
         }
         if (mVersionCode != versionCode) {
-            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
                     + " version code " + versionCode + " inconsistent with "
                     + mVersionCode);
         }
         if (!Signature.areExactMatch(mSignatures, signatures)) {
-            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     tag + " signatures are inconsistent");
         }
     }
@@ -389,14 +432,14 @@
      * Application is already installed; splice existing files that haven't been
      * overridden into our stage.
      */
-    private void spliceExistingFilesIntoStage() throws InstallFailedException {
+    private void spliceExistingFilesIntoStage() throws PackageManagerException {
         final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
         final File existingDir = new File(app.getBaseCodePath());
 
         try {
             linkTreeIgnoringExisting(existingDir, sessionStageDir);
         } catch (ErrnoException e) {
-            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                     "Failed to splice into stage");
         }
     }
@@ -425,22 +468,40 @@
     @Override
     public void destroy() {
         try {
-            synchronized (mLock) {
-                mInvalid = true;
-            }
-            FileUtils.deleteContents(sessionStageDir);
-            sessionStageDir.delete();
+            destroyInternal();
         } finally {
-            mCallback.onSessionInvalid(this);
+            mCallback.onSessionFinished(this, false);
         }
     }
 
-    private class InstallFailedException extends Exception {
-        private final int error;
-
-        public InstallFailedException(int error, String detailMessage) {
-            super(detailMessage);
-            this.error = error;
+    private void destroyInternal() {
+        synchronized (mLock) {
+            mInvalid = true;
         }
+        FileUtils.deleteContents(sessionStageDir);
+        sessionStageDir.delete();
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("Session " + sessionId + ":");
+        pw.increaseIndent();
+
+        pw.printPair("userId", userId);
+        pw.printPair("installerPackageName", installerPackageName);
+        pw.printPair("installerUid", installerUid);
+        pw.printPair("createdMillis", createdMillis);
+        pw.printPair("sessionStageDir", sessionStageDir);
+        pw.println();
+
+        params.dump(pw);
+
+        pw.printPair("mClientProgress", mClientProgress);
+        pw.printPair("mProgress", mProgress);
+        pw.printPair("mMutationsAllowed", mMutationsAllowed);
+        pw.printPair("mPermissionsConfirmed", mPermissionsConfirmed);
+        pw.printPair("mBridges", mBridges.size());
+        pw.println();
+
+        pw.decreaseIndent();
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
new file mode 100644
index 0000000..0cbdcdc
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+/** {@hide} */
+public class PackageManagerException extends Exception {
+    public final int error;
+
+    public PackageManagerException(int error, String detailMessage) {
+        super(detailMessage);
+        this.error = error;
+    }
+
+    public PackageManagerException(int error, String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+        this.error = error;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d8f4f78..101ef92 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24,14 +24,30 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 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;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
 import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Process.PACKAGE_INFO_GID;
 import static android.os.Process.SYSTEM_UID;
 import static android.system.OsConstants.O_CREAT;
-import static android.system.OsConstants.EEXIST;
-import static android.system.OsConstants.O_EXCL;
 import static android.system.OsConstants.O_RDWR;
 import static android.system.OsConstants.S_IRGRP;
 import static android.system.OsConstants.S_IROTH;
@@ -54,6 +70,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
 import com.android.server.IntentResolver;
@@ -85,18 +102,17 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstallSessionParams;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.ManifestDigest;
 import android.content.pm.PackageCleanItem;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
-import android.content.pm.PackageInstallerParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser.ActivityIntentInfo;
 import android.content.pm.PackageParser.PackageLite;
@@ -185,7 +201,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
@@ -381,9 +396,6 @@
     final HashMap<String, PackageParser.Package> mAppDirs =
             new HashMap<String, PackageParser.Package>();
 
-    // Information for the parser to write more useful error messages.
-    int mLastScanError;
-
     // ----------------------------------------------------------------
 
     // Keys are String (package name), values are Package.  This also serves
@@ -1080,7 +1092,8 @@
                         if (args.observer != null) {
                             try {
                                 Bundle extras = extrasForInstallResult(res);
-                                args.observer.packageInstalled(res.name, extras, res.returnCode);
+                                args.observer.packageInstalled(res.name, extras, res.returnCode,
+                                        res.returnMsg);
                             } catch (RemoteException e) {
                                 Slog.i(TAG, "Observer no longer exists.");
                             }
@@ -2815,7 +2828,9 @@
             // Migrate the old signatures to the new scheme.
             existingSigs.assignSignatures(scannedPkg.mSignatures);
             // The new KeySets will be re-added later in the scanning process.
-            mSettings.mKeySetManagerService.removeAppKeySetData(scannedPkg.packageName);
+            synchronized (mPackages) {
+                mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
+            }
             return PackageManager.SIGNATURE_MATCH;
         }
         return PackageManager.SIGNATURE_NO_MATCH;
@@ -4094,14 +4109,19 @@
                 // Ignore entries which are not apk's
                 continue;
             }
-            PackageParser.Package pkg = scanPackageLI(file,
-                    flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null, null);
-            // Don't mess around with apps in system partition.
-            if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
-                    mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
-                // Delete the apk
-                Slog.w(TAG, "Cleaning up failed install of " + file);
-                file.delete();
+            try {
+                scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime,
+                        null, null);
+            } catch (PackageManagerException e) {
+                Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
+
+                // Don't mess around with apps in system partition.
+                if ((flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
+                        e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
+                    // Delete the apk
+                    Slog.w(TAG, "Cleaning up failed install of " + file);
+                    file.delete();
+                }
             }
         }
     }
@@ -4131,8 +4151,9 @@
         Slog.println(priority, TAG, msg);
     }
 
-    private boolean collectCertificatesLI(PackageParser pp, PackageSetting ps,
-            PackageParser.Package pkg, File srcFile, int parseFlags) {
+    private void collectCertificatesLI(PackageParser pp, PackageSetting ps,
+            PackageParser.Package pkg, File srcFile, int parseFlags)
+            throws PackageManagerException {
         if (ps != null
                 && ps.codePath.equals(srcFile)
                 && ps.timeStamp == srcFile.lastModified()
@@ -4145,11 +4166,14 @@
                 // if the package appears to be unchanged.
                 pkg.mSignatures = ps.signatures.mSignatures;
                 KeySetManagerService ksms = mSettings.mKeySetManagerService;
-                pkg.mSigningKeys = ksms.getPublicKeysFromKeySet(mSigningKeySetId);
-                return true;
+                synchronized (mPackages) {
+                    pkg.mSigningKeys = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
+                }
+                return;
             }
 
-            Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures.  Collecting certs again to recover them.");
+            Slog.w(TAG, "PackageSetting for " + ps.name
+                    + " is missing signatures.  Collecting certs again to recover them.");
         } else {
             Log.i(TAG, srcFile.toString() + " changed; collecting certs");
         }
@@ -4158,20 +4182,17 @@
             pp.collectCertificates(pkg, parseFlags);
             pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
-            Slog.e(TAG, "Failed during collect: " + e);
-            mLastScanError = e.error;
-            return false;
+            throw new PackageManagerException(e.error, "Failed to collect certificates for "
+                    + pkg.packageName + ": " + e.getMessage());
         }
-        return true;
     }
 
     /*
      *  Scan a package and return the newly parsed package.
      *  Returns null in case of errors and the error code is stored in mLastScanError
      */
-    private PackageParser.Package scanPackageLI(File scanFile,
-            int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) {
-        mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+    private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanMode,
+            long currentTime, UserHandle user, String abiOverride) throws PackageManagerException {
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
         parseFlags |= mDefParseFlags;
         PackageParser pp = new PackageParser();
@@ -4187,9 +4208,8 @@
         try {
             pkg = pp.parsePackage(scanFile, parseFlags);
         } catch (PackageParserException e) {
-            Slog.e(TAG, "Failed during scan: " + e);
-            mLastScanError = e.error;
-            return null;
+            throw new PackageManagerException(e.error,
+                    "Failed to scan " + scanFile + ": " + e.getMessage());
         }
 
         PackageSetting ps = null;
@@ -4241,8 +4261,7 @@
                         }
                     }
                     updatedPkg.pkg = pkg;
-                    mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
-                    return null;
+                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, null);
                 } else {
                     // The current app on the system partition is better than
                     // what we have updated to on the data partition; switch
@@ -4285,11 +4304,9 @@
                 parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
             }
         }
+
         // Verify certificates against what was last scanned
-        if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags)) {
-            Slog.w(TAG, "Failed verifying certificates for package:" + pkg.packageName);
-            return null;
-        }
+        collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);
 
         /*
          * A new system app appeared, but we already had a non-system one of the
@@ -4401,7 +4418,8 @@
         return processName;
     }
 
-    private boolean verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg) {
+    private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
+            throws PackageManagerException {
         if (pkgSetting.signatures.mSignatures != null) {
             // Already existing package. Make sure signatures match
             boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
@@ -4411,10 +4429,9 @@
                         == PackageManager.SIGNATURE_MATCH;
             }
             if (!match) {
-                Slog.e(TAG, "Package " + pkg.packageName
-                        + " signatures do not match the previously installed version; ignoring!");
-                mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-                return false;
+                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+                        + pkg.packageName + " signatures do not match the "
+                        + "previously installed version; ignoring!");
             }
         }
 
@@ -4428,14 +4445,12 @@
                         == PackageManager.SIGNATURE_MATCH;
             }
             if (!match) {
-                Slog.e(TAG, "Package " + pkg.packageName
+                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+                        "Package " + pkg.packageName
                         + " has no signatures that match those in shared user "
                         + pkgSetting.sharedUser.name + "; ignoring!");
-                mLastScanError = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
-                return false;
             }
         }
-        return true;
     }
 
     /**
@@ -4617,56 +4632,70 @@
             }
         }
 
-        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
-            final Collection<String> paths = pkg.getAllCodePaths();
-            for (String path : paths) {
-                for (String instructionSet : instructionSets) {
-                    try {
-                        boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path,
-                                pkg.packageName, instructionSet, defer);
-                        // There are three basic cases here:
-                        // 1.) we need to dexopt, either because we are forced or it is needed
-                        // 2.) we are defering a needed dexopt
-                        // 3.) we are skipping an unneeded dexopt
-                        if (forceDex || (!defer && isDexOptNeededInternal)) {
-                            Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);
-                            final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
-                            int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
-                                    pkg.packageName, instructionSet);
-                            // Note that we ran dexopt, since rerunning will
-                            // probably just result in an error again.
+        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+            return DEX_OPT_SKIPPED;
+        }
+
+        final Collection<String> paths = pkg.getAllCodePaths();
+        boolean performedDexOpt = false;
+        // There are three basic cases here:
+        // 1.) we need to dexopt, either because we are forced or it is needed
+        // 2.) we are defering a needed dexopt
+        // 3.) we are skipping an unneeded dexopt
+        for (String path : paths) {
+            for (String instructionSet : instructionSets) {
+                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);
+                        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+                        final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
+                                pkg.packageName, instructionSet);
+
+                        if (ret < 0) {
+                            // 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;
-                            if (ret < 0) {
-                                return DEX_OPT_FAILED;
-                            }
-                            return DEX_OPT_PERFORMED;
+                            return DEX_OPT_FAILED;
+                        } else {
+                            performedDexOpt = true;
                         }
-                        if (defer && isDexOptNeededInternal) {
-                            if (mDeferredDexOpt == null) {
-                                mDeferredDexOpt = new HashSet<PackageParser.Package>();
-                            }
-                            mDeferredDexOpt.add(pkg);
-                            return DEX_OPT_DEFERRED;
-                        }
-                        pkg.mDexOptNeeded = false;
-                        return DEX_OPT_SKIPPED;
-                    } catch (FileNotFoundException e) {
-                        Slog.w(TAG, "Apk not found for dexopt: " + path);
-                        return DEX_OPT_FAILED;
-                    } catch (IOException e) {
-                        Slog.w(TAG, "IOException reading apk: " + path, e);
-                        return DEX_OPT_FAILED;
-                    } catch (StaleDexCacheError e) {
-                        Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
-                        return DEX_OPT_FAILED;
-                    } catch (Exception e) {
-                        Slog.w(TAG, "Exception when doing dexopt : ", e);
-                        return DEX_OPT_FAILED;
                     }
+
+                    // We're deciding to defer a needed dexopt. Don't bother dexopting for other
+                    // paths and instruction sets. We'll deal with them all together when we process
+                    // our list of deferred dexopts.
+                    if (defer && isDexOptNeeded) {
+                        if (mDeferredDexOpt == null) {
+                            mDeferredDexOpt = new HashSet<PackageParser.Package>();
+                        }
+                        mDeferredDexOpt.add(pkg);
+                        return DEX_OPT_DEFERRED;
+                    }
+                } catch (FileNotFoundException e) {
+                    Slog.w(TAG, "Apk not found for dexopt: " + path);
+                    return DEX_OPT_FAILED;
+                } catch (IOException e) {
+                    Slog.w(TAG, "IOException reading apk: " + path, e);
+                    return DEX_OPT_FAILED;
+                } catch (StaleDexCacheError e) {
+                    Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
+                    return DEX_OPT_FAILED;
+                } catch (Exception e) {
+                    Slog.w(TAG, "Exception when doing dexopt : ", e);
+                    return DEX_OPT_FAILED;
                 }
             }
         }
-        return DEX_OPT_SKIPPED;
+
+        // If we've gotten here, we're sure that no error occurred and that we haven't
+        // 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;
     }
 
     private static String[] getAppDexInstructionSets(ApplicationInfo info) {
@@ -4687,9 +4716,12 @@
     private static String[] getAppDexInstructionSets(PackageSetting ps) {
         if (ps.primaryCpuAbiString != null) {
             if (ps.secondaryCpuAbiString != null) {
-                return new String[] { ps.primaryCpuAbiString, ps.secondaryCpuAbiString };
+                return new String[] {
+                        VMRuntime.getInstructionSet(ps.primaryCpuAbiString),
+                        VMRuntime.getInstructionSet(ps.secondaryCpuAbiString) };
             } else {
-                return new String[] { ps.primaryCpuAbiString };
+                return new String[] {
+                        VMRuntime.getInstructionSet(ps.primaryCpuAbiString) };
             }
         }
 
@@ -4815,8 +4847,8 @@
         }
     }
 
-    private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
-            PackageParser.Package changingLib) {
+    private void updateSharedLibrariesLPw(PackageParser.Package pkg,
+            PackageParser.Package changingLib) throws PackageManagerException {
         // We might be upgrading from a version of the platform that did not
         // provide per-package native library directories for system apps.
         // Fix that up here.
@@ -4833,11 +4865,9 @@
             for (int i=0; i<N; i++) {
                 final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
                 if (file == null) {
-                    Slog.e(TAG, "Package " + pkg.packageName
-                            + " requires unavailable shared library "
+                    throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                            "Package " + pkg.packageName + " requires unavailable shared library "
                             + pkg.usesLibraries.get(i) + "; failing!");
-                    mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
-                    return false;
                 }
                 addSharedLibraryLPw(usesLibraryFiles, file, changingLib);
             }
@@ -4859,7 +4889,6 @@
                 pkg.usesLibraryFiles = null;
             }
         }
-        return true;
     }
 
     private static boolean hasString(List<String> list, List<String> which) {
@@ -4878,7 +4907,11 @@
 
     private void updateAllSharedLibrariesLPw() {
         for (PackageParser.Package pkg : mPackages.values()) {
-            updateSharedLibrariesLPw(pkg, null);
+            try {
+                updateSharedLibrariesLPw(pkg, null);
+            } catch (PackageManagerException e) {
+                Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
+            }
         }
     }
 
@@ -4892,21 +4925,25 @@
                     res = new ArrayList<PackageParser.Package>();
                 }
                 res.add(pkg);
-                updateSharedLibrariesLPw(pkg, changingPkg);
+                try {
+                    updateSharedLibrariesLPw(pkg, changingPkg);
+                } catch (PackageManagerException e) {
+                    Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
+                }
             }
         }
         return res;
     }
 
-    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
-            int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) {
+    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
+            int scanMode, long currentTime, UserHandle user, String abiOverride)
+            throws PackageManagerException {
         final File scanFile = new File(pkg.codePath);
         if (pkg.applicationInfo.getCodePath() == null ||
                 pkg.applicationInfo.getResourcePath() == null) {
             // Bail out. The resource and code paths haven't been set.
-            Slog.w(TAG, " Code and resource paths haven't been set correctly");
-            mLastScanError = PackageManager.INSTALL_FAILED_INVALID_APK;
-            return null;
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                    "Code and resource paths haven't been set correctly");
         }
 
         if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
@@ -4929,8 +4966,8 @@
                     Slog.w(TAG, "Core android package being redefined.  Skipping.");
                     Slog.w(TAG, " file=" + scanFile);
                     Slog.w(TAG, "*************************************************");
-                    mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
-                    return null;
+                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                            "Core android package being redefined.  Skipping.");
                 }
 
                 // Set up information for our fall-back user intent resolution activity.
@@ -4966,10 +5003,9 @@
 
         if (mPackages.containsKey(pkg.packageName)
                 || mSharedLibraries.containsKey(pkg.packageName)) {
-            Slog.w(TAG, "Application package " + pkg.packageName
+            throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                    "Application package " + pkg.packageName
                     + " already installed.  Skipping duplicate.");
-            mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
-            return null;
         }
 
         // Initialize package source and resource directories
@@ -4991,10 +5027,9 @@
             if (pkg.mSharedUserId != null) {
                 suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, true);
                 if (suid == null) {
-                    Slog.w(TAG, "Creating application package " + pkg.packageName
+                    throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                            "Creating application package " + pkg.packageName
                             + " for shared user failed");
-                    mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-                    return null;
                 }
                 if (DEBUG_PACKAGE_SCANNING) {
                     if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
@@ -5066,11 +5101,10 @@
                     pkg.applicationInfo.secondaryCpuAbi,
                     pkg.applicationInfo.flags, user, false);
             if (pkgSetting == null) {
-                Slog.w(TAG, "Creating application package " + pkg.packageName + " failed");
-                mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-                return null;
+                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                        "Creating application package " + pkg.packageName + " failed");
             }
-            
+
             if (pkgSetting.origPackage != null) {
                 // If we are first transitioning from an original package,
                 // fix up the new package's name now.  We need to do this after
@@ -5105,9 +5139,7 @@
                 // are the only ones that can fail an install due to this.  We
                 // will take care of the system apps by updating all of their
                 // library paths after the scan is done.
-                if (!updateSharedLibrariesLPw(pkg, null)) {
-                    return null;
-                }
+                updateSharedLibrariesLPw(pkg, null);
             }
 
             if (mFoundPolicyFile) {
@@ -5117,9 +5149,11 @@
             pkg.applicationInfo.uid = pkgSetting.appId;
             pkg.mExtras = pkgSetting;
             if (!pkgSetting.keySetData.isUsingUpgradeKeySets() || pkgSetting.sharedUser != null) {
-                if (!verifySignaturesLP(pkgSetting, pkg)) {
-                    if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
-                        return null;
+                try {
+                    verifySignaturesLP(pkgSetting, pkg);
+                } catch (PackageManagerException e) {
+                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw e;
                     }
                     // The signature has changed, but this package is in the system
                     // image...  let's recover!
@@ -5132,9 +5166,10 @@
                     if (pkgSetting.sharedUser != null) {
                         if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                                               pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
-                            Log.w(TAG, "Signature mismatch for shared user : " + pkgSetting.sharedUser);
-                            mLastScanError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
-                            return null;
+                            throw new PackageManagerException(
+                                    INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                            "Signature mismatch for shared user : "
+                                            + pkgSetting.sharedUser);
                         }
                     }
                     // File a report about this.
@@ -5144,10 +5179,9 @@
                 }
             } else {
                 if (!checkUpgradeKeySetLP(pkgSetting, pkg)) {
-                    Slog.e(TAG, "Package " + pkg.packageName
-                           + " upgrade keys do not match the previously installed version; ");
-                    mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-                    return null;
+                    throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+                            + pkg.packageName + " upgrade keys do not match the "
+                            + "previously installed version");
                 } else {
                     // signatures may have changed as result of upgrade
                     pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -5167,13 +5201,14 @@
                         for (int j = 0; j < names.length; j++) {
                             if (mProvidersByAuthority.containsKey(names[j])) {
                                 PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
-                                Slog.w(TAG, "Can't install because provider name " + names[j] +
-                                        " (in package " + pkg.applicationInfo.packageName +
-                                        ") is already used by "
-                                        + ((other != null && other.getComponentName() != null)
-                                                ? other.getComponentName().getPackageName() : "?"));
-                                mLastScanError = PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
-                                return null;
+                                final String otherPackageName =
+                                        ((other != null && other.getComponentName() != null) ?
+                                                other.getComponentName().getPackageName() : "?");
+                                throw new PackageManagerException(
+                                        INSTALL_FAILED_CONFLICTING_PROVIDER,
+                                                "Can't install because provider name " + names[j]
+                                                + " (in package " + pkg.applicationInfo.packageName
+                                                + ") is already used by " + otherPackageName);
                             }
                         }
                     }
@@ -5269,8 +5304,8 @@
                                 msg = prefix + pkg.packageName
                                         + " could not have data directory re-created after delete.";
                                 reportSettingsProblem(Log.WARN, msg);
-                                mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-                                return null;
+                                throw new PackageManagerException(
+                                        INSTALL_FAILED_INSUFFICIENT_STORAGE, msg);
                             }
                         }
                         if (!recovered) {
@@ -5279,8 +5314,8 @@
                     } else if (!recovered) {
                         // If we allow this install to proceed, we will be broken.
                         // Abort, abort!
-                        mLastScanError = PackageManager.INSTALL_FAILED_UID_CHANGED;
-                        return null;
+                        throw new PackageManagerException(INSTALL_FAILED_UID_CHANGED,
+                                "scanPackageLI");
                     }
                     if (!recovered) {
                         pkg.applicationInfo.dataDir = "/mismatched_uid/settings_"
@@ -5319,9 +5354,8 @@
                                            pkg.applicationInfo.seinfo);
                 if (ret < 0) {
                     // Error from installer
-                    Slog.w(TAG, "Unable to create data dirs [errorCode=" + ret + "]");
-                    mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-                    return null;
+                    throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                            "Unable to create data dirs [errorCode=" + ret + "]");
                 }
 
                 if (dataPath.exists()) {
@@ -5395,9 +5429,9 @@
                     }
 
                     if (abi32 < 0 && abi32 != PackageManager.NO_NATIVE_LIBRARIES) {
-                        Slog.w(TAG, "Error unpackaging 32 bit native libs for multiarch app, errorCode=" + abi32);
-                        mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-                        return null;
+                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                                "Error unpackaging 32 bit native libs for multiarch app, errorCode="
+                                + abi32);
                     }
 
                     if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
@@ -5410,12 +5444,11 @@
                     }
 
                     if (abi64 < 0 && abi64 != PackageManager.NO_NATIVE_LIBRARIES) {
-                        Slog.w(TAG, "Error unpackaging 64 bit native libs for multiarch app, errorCode=" + abi32);
-                        mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-                        return null;
+                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                                "Error unpackaging 64 bit native libs for multiarch app, errorCode="
+                                + abi32);
                     }
 
-
                     if (abi64 >= 0) {
                         pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
                     }
@@ -5450,9 +5483,8 @@
                     }
 
                     if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
-                        Slog.w(TAG, "Error unpackaging native libs for app, errorCode=" + copyRet);
-                        mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-                        return null;
+                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                                "Error unpackaging native libs for app, errorCode=" + copyRet);
                     }
 
                     if (copyRet >= 0) {
@@ -5480,10 +5512,8 @@
                     final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
                     for (int userId : userIds) {
                         if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) {
-                            Slog.w(TAG, "Failed linking native library dir (user=" + userId
-                                    + ")");
-                            mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-                            return null;
+                            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                                    "Failed linking native library dir (user=" + userId + ")");
                         }
                     }
                 }
@@ -5516,8 +5546,8 @@
             // code and package path correct.
             if (!adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
                     pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0)) {
-                mLastScanError = PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
-                return null;
+                throw new PackageManagerException(INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
+                        "scanPackageLI");
             }
         }
 
@@ -5528,8 +5558,7 @@
                     removeDataDirsLI(pkg.packageName);
                 }
 
-                mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
-                return null;
+                throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI");
             }
         }
 
@@ -5608,8 +5637,8 @@
                             removeDataDirsLI(pkg.packageName);
                         }
 
-                        mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
-                        return null;
+                        throw new PackageManagerException(INSTALL_FAILED_DEXOPT,
+                                "scanPackageLI failed to dexopt clientLibPkgs");
                     }
                 }
             }
@@ -5687,19 +5716,19 @@
             KeySetManagerService ksms = mSettings.mKeySetManagerService;
             try {
                 // Old KeySetData no longer valid.
-                ksms.removeAppKeySetData(pkg.packageName);
-                ksms.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys);
+                ksms.removeAppKeySetDataLPw(pkg.packageName);
+                ksms.addSigningKeySetToPackageLPw(pkg.packageName, pkg.mSigningKeys);
                 if (pkg.mKeySetMapping != null) {
                     for (Map.Entry<String, ArraySet<PublicKey>> entry :
                             pkg.mKeySetMapping.entrySet()) {
                         if (entry.getValue() != null) {
-                            ksms.addDefinedKeySetToPackage(pkg.packageName,
+                            ksms.addDefinedKeySetToPackageLPw(pkg.packageName,
                                                           entry.getValue(), entry.getKey());
                         }
                     }
                     if (pkg.mUpgradeKeySets != null) {
                         for (String upgradeAlias : pkg.mUpgradeKeySets) {
-                            ksms.addUpgradeKeySetToPackage(pkg.packageName, upgradeAlias);
+                            ksms.addUpgradeKeySetToPackageLPw(pkg.packageName, upgradeAlias);
                         }
                     }
                 }
@@ -5991,8 +6020,8 @@
                     map.put(pkg.packageName, pkg);
                     PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget);
                     if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) {
-                        mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-                        return null;
+                        throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                "scanPackageLI failed to createIdmap");
                     }
                 }
             } else if (mOverlays.containsKey(pkg.packageName) &&
@@ -6287,7 +6316,7 @@
                 Slog.e(TAG, "Package: " + pkg + " has multiple bundled libs, but is not multiarch.");
             }
 
-            if (VMRuntime.is64BitAbi(getPreferredInstructionSet())) {
+            if (VMRuntime.is64BitInstructionSet(getPreferredInstructionSet())) {
                 pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
                 pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
             } else {
@@ -6343,8 +6372,7 @@
                 subDir = nativeLibraryRoot;
             }
 
-            int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle,
-                    subDir, Build.SUPPORTED_ABIS[abi]);
+            int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, subDir, abiList[abi]);
             if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
                 return copyRet;
             }
@@ -7672,9 +7700,14 @@
                                 flags |= PackageParser.PARSE_IS_PRIVILEGED;
                             }
                         }
-                        p = scanPackageLI(fullPath, flags,
-                                SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
-                                System.currentTimeMillis(), UserHandle.ALL, null);
+                        try {
+                            p = scanPackageLI(fullPath, flags,
+                                    SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
+                                    System.currentTimeMillis(), UserHandle.ALL, null);
+                        } catch (PackageManagerException e) {
+                            Slog.w(TAG, "Failed to scan " + fullPath + ": " + e.getMessage());
+                            p = null;
+                        }
                         if (p != null) {
                             /*
                              * TODO this seems dangerous as the package may have
@@ -7730,7 +7763,7 @@
         if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
             try {
                 if (observer != null) {
-                    observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED);
+                    observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED, null);
                 }
             } catch (RemoteException re) {
             }
@@ -7763,7 +7796,7 @@
     }
 
     void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer,
-            PackageInstallerParams params, String installerPackageName, int installerUid,
+            InstallSessionParams params, String installerPackageName, int installerUid,
             UserHandle user) {
         final VerificationParams verifParams = new VerificationParams(null, params.originatingUri,
                 params.referrerUri, installerUid, null);
@@ -9823,8 +9856,15 @@
         int[] newUsers;
         PackageParser.Package pkg;
         int returnCode;
+        String returnMsg;
         PackageRemovedInfo removedInfo;
 
+        public void setError(int code, String msg) {
+            returnCode = code;
+            returnMsg = msg;
+            Slog.w(TAG, msg);
+        }
+
         // In some error cases we want to convey more info back to the observer
         String origPackage;
         String origPermission;
@@ -9847,29 +9887,23 @@
                 // it has been renamed to an older name.  The package we
                 // are trying to install should be installed as an update to
                 // the existing one, but that has not been requested, so bail.
-                Slog.w(TAG, "Attempt to re-install " + pkgName
+                res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
                         + " without first uninstalling package running as "
                         + mSettings.mRenamedPackages.get(pkgName));
-                res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                 return;
             }
             if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.codePath)) {
                 // Don't allow installation over an existing package with the same name.
-                Slog.w(TAG, "Attempt to re-install " + pkgName
+                res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
                         + " without first uninstalling.");
-                res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                 return;
             }
         }
-        mLastScanError = PackageManager.INSTALL_SUCCEEDED;
-        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
-                System.currentTimeMillis(), user, abiOverride);
-        if (newPackage == null) {
-            Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
-            if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
-                res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
-            }
-        } else {
+
+        try {
+            PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
+                    System.currentTimeMillis(), user, abiOverride);
+
             updateSettingsLI(newPackage, installerPackageName, null, null, res);
             // delete the partially installed application. the data directory will have to be
             // restored if it was already existing
@@ -9882,6 +9916,10 @@
                         dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
                                 res.removedInfo, true);
             }
+
+        } catch (PackageManagerException e) {
+            res.setError(e.error,
+                    "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
         }
     }
 
@@ -9890,14 +9928,9 @@
         // required keys.
         long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
         KeySetManagerService ksms = mSettings.mKeySetManagerService;
-        Set<Long> newSigningKeyIds = new ArraySet<Long>();
-        for (PublicKey pk : newPkg.mSigningKeys) {
-            newSigningKeyIds.add(ksms.getIdForPublicKey(pk));
-        }
-        //remove PUBLIC_KEY_NOT_FOUND, although not necessary
-        newSigningKeyIds.remove(ksms.PUBLIC_KEY_NOT_FOUND);
         for (int i = 0; i < upgradeKeySets.length; i++) {
-            if (newSigningKeyIds.containsAll(ksms.mKeySetMapping.get(upgradeKeySets[i]))) {
+            Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
+            if (newPkg.mSigningKeys.containsAll(upgradeSet)) {
                 return true;
             }
         }
@@ -9921,15 +9954,15 @@
                 // default to original signature matching
                 if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
                     != PackageManager.SIGNATURE_MATCH) {
-                    Slog.w(TAG, "New package has a different signature: " + pkgName);
-                    res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+                    res.setError(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                            "New package has a different signature: " + pkgName);
                     return;
                 }
             } else {
                 if(!checkUpgradeKeySetLP(ps, pkg)) {
-                    Slog.w(TAG, "New package not signed by keys specified by upgrade-keysets: "
-                           + pkgName);
-                    res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+                    res.setError(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                            "New package not signed by keys specified by upgrade-keysets: "
+                            + pkgName);
                     return;
                 }
             }
@@ -9957,7 +9990,6 @@
             PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
             int[] allUsers, boolean[] perUserInstalled,
             String installerPackageName, PackageInstalledInfo res, String abiOverride) {
-        PackageParser.Package newPackage = null;
         String pkgName = deletedPackage.packageName;
         boolean deletedPkg = true;
         boolean updatedSettings = false;
@@ -9975,21 +10007,18 @@
         if (!deletePackageLI(pkgName, null, true, null, null, PackageManager.DELETE_KEEP_DATA,
                 res.removedInfo, true)) {
             // If the existing package wasn't successfully deleted
-            res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+            res.setError(INSTALL_FAILED_REPLACE_COULDNT_DELETE, "replaceNonSystemPackageLI");
             deletedPkg = false;
         } else {
             // Successfully deleted the old package. Now proceed with re-installation
-            mLastScanError = PackageManager.INSTALL_SUCCEEDED;
-            newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME,
-                    System.currentTimeMillis(), user, abiOverride);
-            if (newPackage == null) {
-                Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
-                if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
-                    res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
-                }
-            } else {
+            try {
+                final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
+                        scanMode | SCAN_UPDATE_TIME, System.currentTimeMillis(), user, abiOverride);
                 updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
                 updatedSettings = true;
+            } catch (PackageManagerException e) {
+                res.setError(e.error,
+                        "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
             }
         }
 
@@ -10017,9 +10046,12 @@
                         (oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0);
                 int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE
                         | SCAN_UPDATE_TIME;
-                if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode,
-                        origUpdateTime, null, null) == null) {
-                    Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade");
+                try {
+                    scanPackageLI(restoreFile, oldParseFlags, oldScanMode, origUpdateTime, null,
+                            null);
+                } catch (PackageManagerException e) {
+                    Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade: "
+                            + e.getMessage());
                     return;
                 }
                 // Restore of old package succeeded. Update permissions.
@@ -10041,7 +10073,6 @@
             String installerPackageName, PackageInstalledInfo res, String abiOverride) {
         if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
                 + ", old=" + deletedPackage);
-        PackageParser.Package newPackage = null;
         boolean updatedSettings = false;
         parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
                 PackageParser.PARSE_IS_SYSTEM;
@@ -10049,9 +10080,9 @@
             parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
         }
         String packageName = deletedPackage.packageName;
-        res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
         if (packageName == null) {
-            Slog.w(TAG, "Attempt to delete null packageName.");
+            res.setError(INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+                    "Attempt to delete null packageName.");
             return;
         }
         PackageParser.Package oldPkg;
@@ -10062,7 +10093,8 @@
             oldPkgSetting = mSettings.mPackages.get(packageName);
             if((oldPkg == null) || (oldPkg.applicationInfo == null) ||
                     (oldPkgSetting == null)) {
-                Slog.w(TAG, "Couldn't find package:"+packageName+" information");
+                res.setError(INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+                        "Couldn't find package:" + packageName + " information");
                 return;
             }
         }
@@ -10091,25 +10123,22 @@
         }
         
         // Successfully disabled the old package. Now proceed with re-installation
-        res.returnCode = mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+        res.returnCode = PackageManager.INSTALL_SUCCEEDED;
         pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-        newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user, abiOverride);
-        if (newPackage == null) {
-            Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
-            if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
-                res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
-            }
-        } else {
+
+        PackageParser.Package newPackage = null;
+        try {
+            newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user, abiOverride);
             if (newPackage.mExtras != null) {
-                final PackageSetting newPkgSetting = (PackageSetting)newPackage.mExtras;
+                final PackageSetting newPkgSetting = (PackageSetting) newPackage.mExtras;
                 newPkgSetting.firstInstallTime = oldPkgSetting.firstInstallTime;
                 newPkgSetting.lastUpdateTime = System.currentTimeMillis();
 
                 // is the update attempting to change shared user? that isn't going to work...
                 if (oldPkgSetting.sharedUser != newPkgSetting.sharedUser) {
-                    Slog.w(TAG, "Forbidding shared user change from " + oldPkgSetting.sharedUser
+                    res.setError(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+                            "Forbidding shared user change from " + oldPkgSetting.sharedUser
                             + " to " + newPkgSetting.sharedUser);
-                    res.returnCode = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
                     updatedSettings = true;
                 }
             }
@@ -10118,6 +10147,10 @@
                 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());
         }
 
         if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -10127,7 +10160,12 @@
                 removeInstalledPackageLI(newPackage, true);
             }
             // Add back the old system package
-            scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user, null);
+            try {
+                scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user,
+                        null);
+            } catch (PackageManagerException e) {
+                Slog.e(TAG, "Failed to restore original package: " + e.getMessage());
+            }
             // Restore the old system information in Settings
             synchronized(mPackages) {
                 if (updatedSettings) {
@@ -10250,15 +10288,14 @@
         try {
             pkg = pp.parsePackage(tmpPackageFile, parseFlags);
         } catch (PackageParserException e) {
-            Slog.e(TAG, "Failed during install: " + e);
-            res.returnCode = e.error;
+            res.setError(e.error, "Failed parse during installPackageLI: " + e.getMessage());
             return;
         }
 
         String pkgName = res.name = pkg.packageName;
         if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
             if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
-                res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
+                res.setError(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
                 return;
             }
         }
@@ -10267,8 +10304,7 @@
             pp.collectCertificates(pkg, parseFlags);
             pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
-            Slog.e(TAG, "Failed during install: " + e);
-            res.returnCode = e.error;
+            res.setError(e.error, "Failed collect during installPackageLI: " + e.getMessage());
             return;
         }
 
@@ -10282,7 +10318,7 @@
             }
 
             if (!args.manifestDigest.equals(pkg.manifestDigest)) {
-                res.returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+                res.setError(INSTALL_FAILED_PACKAGE_CHANGED, "Manifest digest changed");
                 return;
             }
         } else if (DEBUG_INSTALL) {
@@ -10310,10 +10346,9 @@
                         // install to proceed; we fail the install on all other permission
                         // redefinitions.
                         if (!bp.sourcePackage.equals("android")) {
-                            Slog.w(TAG, "Package " + pkg.packageName
-                                    + " attempting to redeclare permission " + perm.info.name
-                                    + " already owned by " + bp.sourcePackage);
-                            res.returnCode = PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
+                            res.setError(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
+                                    + pkg.packageName + " attempting to redeclare permission "
+                                    + perm.info.name + " already owned by " + bp.sourcePackage);
                             res.origPermission = perm.info.name;
                             res.origPackage = bp.sourcePackage;
                             return;
@@ -10363,13 +10398,13 @@
 
         if (systemApp && onSd) {
             // Disable updates to system apps on sdcard
-            Slog.w(TAG, "Cannot install updates to system apps on sdcard");
-            res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+            res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+                    "Cannot install updates to system apps on sdcard");
             return;
         }
 
         if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
-            res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+            res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
             return;
         }
 
@@ -10490,6 +10525,26 @@
             return;
         }
 
+        boolean blocked = false;
+        if ((flags & PackageManager.DELETE_ALL_USERS) != 0) {
+            int[] users = sUserManager.getUserIds();
+            for (int i = 0; i < users.length; ++i) {
+                if (getBlockUninstallForUser(packageName, users[i])) {
+                    blocked = true;
+                    break;
+                }
+            }
+        } else {
+            blocked = getBlockUninstallForUser(packageName, userId);
+        }
+        if (blocked) {
+            try {
+                observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_OWNER_BLOCKED);
+            } catch (RemoteException re) {
+            }
+            return;
+        }
+
         if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
         // Queue up an async operation since the package deletion may take a little while.
         mHandler.post(new Runnable() {
@@ -10671,7 +10726,7 @@
             if (deletedPs != null) {
                 if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
                     if (outInfo != null) {
-                        mSettings.mKeySetManagerService.removeAppKeySetData(packageName);
+                        mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
                         outInfo.removedAppId = mSettings.removePackageLPw(packageName);
                     }
                     if (deletedPs != null) {
@@ -10781,14 +10836,16 @@
         if (locationIsPrivileged(disabledPs.codePath)) {
             parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
         }
-        PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
-                parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null, null);
 
-        if (newPkg == null) {
-            Slog.w(TAG, "Failed to restore system package:" + newPs.name
-                    + " with error:" + mLastScanError);
+        final PackageParser.Package newPkg;
+        try {
+            newPkg = scanPackageLI(disabledPs.codePath, parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0,
+                    null, null);
+        } catch (PackageManagerException e) {
+            Slog.w(TAG, "Failed to restore system package:" + newPs.name + ": " + e.getMessage());
             return false;
         }
+
         // writer
         synchronized (mPackages) {
             PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
@@ -11960,30 +12017,19 @@
 
     static class DumpState {
         public static final int DUMP_LIBS = 1 << 0;
-
         public static final int DUMP_FEATURES = 1 << 1;
-
         public static final int DUMP_RESOLVERS = 1 << 2;
-
         public static final int DUMP_PERMISSIONS = 1 << 3;
-
         public static final int DUMP_PACKAGES = 1 << 4;
-
         public static final int DUMP_SHARED_USERS = 1 << 5;
-
         public static final int DUMP_MESSAGES = 1 << 6;
-
         public static final int DUMP_PROVIDERS = 1 << 7;
-
         public static final int DUMP_VERIFIERS = 1 << 8;
-
         public static final int DUMP_PREFERRED = 1 << 9;
-
         public static final int DUMP_PREFERRED_XML = 1 << 10;
-
         public static final int DUMP_KEYSETS = 1 << 11;
-
         public static final int DUMP_VERSION = 1 << 12;
+        public static final int DUMP_INSTALLS = 1 << 13;
 
         public static final int OPTION_SHOW_FILTERS = 1 << 0;
 
@@ -12087,6 +12133,7 @@
                 pw.println("    version: print database version info");
                 pw.println("    write: write current settings now");
                 pw.println("    <package.name>: info about given package");
+                pw.println("    installs: details about install sessions");
                 return;
             } else if ("--checkin".equals(opt)) {
                 checkin = true;
@@ -12143,6 +12190,8 @@
                     pw.println("Settings written.");
                     return;
                 }
+            } else if ("installs".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_INSTALLS);
             }
         }
 
@@ -12347,7 +12396,7 @@
             }
 
             if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
-                mSettings.mKeySetManagerService.dump(pw, packageName, dumpState);
+                mSettings.mKeySetManagerService.dumpLPr(pw, packageName, dumpState);
             }
 
             if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
@@ -12358,9 +12407,13 @@
                 mSettings.dumpSharedUsersLPr(pw, packageName, dumpState);
             }
 
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS)) {
+                if (dumpState.onTitlePrinted()) pw.println();
+                mInstallerService.dump(new IndentingPrintWriter(pw, "  ", 120));
+            }
+
             if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
-                if (dumpState.onTitlePrinted())
-                    pw.println();
+                if (dumpState.onTitlePrinted()) pw.println();
                 mSettings.dumpReadMessagesLPr(pw, dumpState);
 
                 pw.println();
@@ -12652,8 +12705,12 @@
 
                 doGc = true;
                 synchronized (mInstallLock) {
-                    final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags,
-                            0, 0, null, null);
+                    PackageParser.Package pkg = null;
+                    try {
+                        pkg = scanPackageLI(new File(codePath), parseFlags, 0, 0, null, null);
+                    } catch (PackageManagerException e) {
+                        Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
+                    }
                     // Scan the package
                     if (pkg != null) {
                         /*
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 64288a5..3882769 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -299,6 +299,9 @@
             if (enabledOnly && !profile.isEnabled()) {
                 continue;
             }
+            if (mRemovingUserIds.get(profile.id)) {
+                continue;
+            }
             users.add(profile);
         }
         return users;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6be4d4f..fac78a9 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -861,6 +861,26 @@
         }
 
         @Override
+        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setCaptionEnabled");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .setCaptionEnabled(enabled);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in setCaptionEnabled", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -1020,14 +1040,13 @@
         }
 
         @Override
+        @SuppressWarnings("resource")
         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-            if (mContext.checkCallingOrSelfPermission(
-                    android.Manifest.permission.DUMP)
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                     != PackageManager.PERMISSION_GRANTED) {
-                pw.println("Permission Denial: can't dump TvInputManager " +
-                        "from from pid=" + Binder.getCallingPid() + ", uid=" +
-                        Binder.getCallingUid());
+                pw.println("Permission Denial: can't dump TvInputManager from pid="
+                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
                 return;
             }
 
@@ -1043,7 +1062,6 @@
                 for (int i = 0; i < mUserStates.size(); i++) {
                     int userId = mUserStates.keyAt(i);
                     UserState userState = getUserStateLocked(userId);
-
                     pw.println("UserState (" + userId + "):");
                     pw.increaseIndent();
 
diff --git a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
index 5bafb52..87626d0 100644
--- a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -52,6 +52,7 @@
 static jmethodID method_reportGeofenceRemoveStatus;
 static jmethodID method_reportGeofencePauseStatus;
 static jmethodID method_reportGeofenceResumeStatus;
+static jmethodID method_reportMeasurementData;
 
 static const GpsInterface* sGpsInterface = NULL;
 static const GpsXtraInterface* sGpsXtraInterface = NULL;
@@ -60,6 +61,7 @@
 static const GpsDebugInterface* sGpsDebugInterface = NULL;
 static const AGpsRilInterface* sAGpsRilInterface = NULL;
 static const GpsGeofencingInterface* sGpsGeofencingInterface = NULL;
+static const GpsMeasurementInterface* sGpsMeasurementInterface = NULL;
 
 // temporary storage for GPS callbacks
 static GpsSvStatus  sGpsSvStatus;
@@ -441,6 +443,10 @@
             "(II)V");
     method_reportGeofencePauseStatus = env->GetMethodID(clazz,"reportGeofencePauseStatus",
             "(II)V");
+    method_reportMeasurementData = env->GetMethodID(
+            clazz,
+            "reportMeasurementData",
+            "(Landroid/location/GpsMeasurementsEvent;)V");
 
     err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
     if (err == 0) {
@@ -464,6 +470,8 @@
             (const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);
         sGpsGeofencingInterface =
             (const GpsGeofencingInterface*)sGpsInterface->get_extension(GPS_GEOFENCING_INTERFACE);
+        sGpsMeasurementInterface =
+            (const GpsMeasurementInterface*)sGpsInterface->get_extension(GPS_MEASUREMENT_INTERFACE);
     }
 }
 
@@ -851,42 +859,500 @@
     return JNI_FALSE;
 }
 
+static jobject translate_gps_clock(JNIEnv* env, GpsClock* clock) {
+    const char* doubleSignature = "(D)V";
+
+    jclass gpsClockClass = env->FindClass("android/location/GpsClock");
+    jmethodID gpsClockCtor = env->GetMethodID(gpsClockClass, "<init>", "()V");
+
+    jobject gpsClockObject = env->NewObject(gpsClockClass, gpsClockCtor);
+    GpsClockFlags flags = clock->flags;
+
+    if (flags & GPS_CLOCK_HAS_LEAP_SECOND) {
+        jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setLeapSecond", "(S)V");
+        env->CallObjectMethod(gpsClockObject, setterMethod, clock->leap_second);
+   }
+
+    jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setTimeInNs", "(J)V");
+    env->CallObjectMethod(gpsClockObject, setterMethod, clock->time_ns);
+
+    if (flags & GPS_CLOCK_HAS_TIME_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsClockClass,
+                "setTimeUncertaintyInNs",
+                doubleSignature);
+        env->CallObjectMethod(gpsClockObject, setterMethod, clock->time_uncertainty_ns);
+    }
+
+    if (flags & GPS_CLOCK_HAS_BIAS) {
+        jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setBiasInNs", doubleSignature);
+        env->CallObjectMethod(gpsClockObject, setterMethod, clock->bias_ns);
+    }
+
+    if (flags & GPS_CLOCK_HAS_BIAS_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsClockClass,
+                "setBiasUncertaintyInNs",
+                doubleSignature);
+        env->CallObjectMethod(gpsClockObject, setterMethod, clock->bias_uncertainty_ns);
+    }
+
+    if (flags & GPS_CLOCK_HAS_DRIFT) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsClockClass,
+                "setDriftInNsPerSec",
+                doubleSignature);
+        env->CallObjectMethod(gpsClockObject, setterMethod, clock->drift_nsps);
+    }
+
+    if (flags & GPS_CLOCK_HAS_DRIFT_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsClockClass,
+                "setDriftUncertaintyInNsPerSec",
+                doubleSignature);
+        env->CallObjectMethod(gpsClockObject, setterMethod, clock->drift_uncertainty_nsps);
+    }
+
+    return gpsClockObject;
+}
+
+static jobject translate_gps_measurement(
+        JNIEnv* env,
+        GpsMeasurement* measurement,
+        uint32_t time_ns) {
+    const char* shortSignature = "(S)V";
+    const char* longSignature = "(J)V";
+    const char* floatSignature = "(F)V";
+    const char* doubleSignature = "(D)V";
+
+    jclass gpsMeasurementClass = env->FindClass("android/location/GpsMeasurement");
+    jmethodID gpsMeasurementCtor = env->GetMethodID(gpsMeasurementClass, "<init>", "()V");
+
+    jobject gpsMeasurementObject = env->NewObject(gpsMeasurementClass, gpsMeasurementCtor);
+    GpsMeasurementFlags flags = measurement->flags;
+
+
+    jmethodID prnSetterMethod = env->GetMethodID(gpsMeasurementClass, "setPrn", "(B)V");
+    env->CallObjectMethod(gpsMeasurementObject, prnSetterMethod, measurement->prn);
+
+    jmethodID localTimeSetterMethod =
+            env->GetMethodID(gpsMeasurementClass, "setLocalTimeInNs", longSignature);
+    env->CallObjectMethod(
+            gpsMeasurementObject,
+            localTimeSetterMethod,
+            time_ns + measurement->time_offset_ns);
+
+    jmethodID receivedGpsTowSetterMethod =
+            env->GetMethodID(gpsMeasurementClass, "setReceivedGpsTowInNs", longSignature);
+    env->CallObjectMethod(
+            gpsMeasurementObject,
+            receivedGpsTowSetterMethod,
+            measurement->received_gps_tow_ns);
+
+    jmethodID cn0SetterMethod = env->GetMethodID(
+            gpsMeasurementClass,
+            "setCn0InDbHz",
+            doubleSignature);
+    env->CallObjectMethod(gpsMeasurementObject, cn0SetterMethod, measurement->c_n0_dbhz);
+
+    jmethodID pseudorangeRateSetterMethod = env->GetMethodID(
+            gpsMeasurementClass,
+            "setPseudorangeRateInMetersPerSec",
+            doubleSignature);
+    env->CallObjectMethod(
+            gpsMeasurementObject,
+            pseudorangeRateSetterMethod,
+            measurement->pseudorange_rate_mpersec);
+
+    jmethodID pseudorangeRateUncertaintySetterMethod = env->GetMethodID(
+            gpsMeasurementClass,
+            "setPseudorangeRateUncertaintyInMetersPerSec",
+            doubleSignature);
+    env->CallObjectMethod(
+            gpsMeasurementObject,
+            pseudorangeRateUncertaintySetterMethod,
+            measurement->pseudorange_rate_uncertainty_mpersec);
+
+    jmethodID accumulatedDeltaRangeSetterMethod = env->GetMethodID(
+            gpsMeasurementClass,
+            "setAccumulatedDeltaRangeInMeters",
+            doubleSignature);
+    env->CallVoidMethod(
+            gpsMeasurementObject,
+            accumulatedDeltaRangeSetterMethod,
+            measurement->accumulated_delta_range_m);
+
+    jmethodID accumulatedDeltaRangeUncertaintySetterMethod = env->GetMethodID(
+            gpsMeasurementClass,
+            "setAccumulatedDeltaRangeUncertaintyInMeters",
+            doubleSignature);
+    env->CallVoidMethod(
+            gpsMeasurementObject,
+            accumulatedDeltaRangeUncertaintySetterMethod,
+            measurement->accumulated_delta_range_uncertainty_m);
+
+
+    if (flags & GPS_MEASUREMENT_HAS_PSEUDORANGE) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setPseudorangeInMeters",
+                doubleSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->pseudorange_m);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_PSEUDORANGE_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setPseudorangeUncertaintyInMeters",
+                doubleSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->pseudorange_uncertainty_m);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_CODE_PHASE) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setCodePhaseInChips",
+                doubleSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->code_phase_chips);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_CODE_PHASE_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setCodePhaseUncertaintyInChips",
+                doubleSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->code_phase_uncertainty_chips);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_CARRIER_FREQUENCY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setCarrierFrequencyInHz",
+                 floatSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->carrier_frequency_hz);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_CARRIER_CYCLES) {
+        jmethodID setterMethod =
+                env->GetMethodID(gpsMeasurementClass, "setCarrierCycles", longSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->carrier_cycles);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_CARRIER_PHASE) {
+        jmethodID setterMethod =
+                env->GetMethodID(gpsMeasurementClass, "setCarrierPhase", doubleSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->carrier_phase);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_CARRIER_PHASE_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setCarrierPhaseUncertainty",
+                doubleSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->carrier_phase_uncertainty);
+    }
+
+    jmethodID lossOfLockSetterMethod =
+            env->GetMethodID(gpsMeasurementClass, "setLossOfLock", shortSignature);
+    env->CallObjectMethod(gpsMeasurementObject, lossOfLockSetterMethod, measurement->loss_of_lock);
+
+    if (flags & GPS_MEASUREMENT_HAS_BIT_NUMBER) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setBitNumber",
+                shortSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->bit_number);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_TIME_FROM_LAST_BIT) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setTimeFromLastBitInNs",
+                longSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->time_from_last_bit_ns);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_DOPPLER_SHIFT) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setDopplerShiftInHz",
+                doubleSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->doppler_shift_hz);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_DOPPLER_SHIFT_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setDopplerShiftUncertaintyInHz",
+                doubleSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->doppler_shift_uncertainty_hz);
+    }
+
+    jmethodID multipathIndicatorSetterMethod = env->GetMethodID(
+            gpsMeasurementClass,
+            "setMultipathIndicator",
+            shortSignature);
+    env->CallObjectMethod(
+            gpsMeasurementObject,
+            multipathIndicatorSetterMethod,
+            measurement->multipath_indicator);
+
+    if (flags & GPS_MEASUREMENT_HAS_SNR) {
+        jmethodID setterMethod =
+                env->GetMethodID(gpsMeasurementClass, "setSnrInDb", doubleSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->snr_db);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_ELEVATION) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setElevationInDeg",
+                doubleSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->elevation_deg);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_ELEVATION_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setElevationUncertaintyInDeg",
+                doubleSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->elevation_uncertainty_deg);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_AZIMUTH) {
+        jmethodID setterMethod =
+                env->GetMethodID(gpsMeasurementClass, "setAzimuthInDeg", doubleSignature);
+        env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->azimuth_deg);
+    }
+
+    if (flags & GPS_MEASUREMENT_HAS_AZIMUTH_UNCERTAINTY) {
+        jmethodID setterMethod = env->GetMethodID(
+                gpsMeasurementClass,
+                "setAzimuthUncertaintyInDeg",
+                doubleSignature);
+        env->CallObjectMethod(
+                gpsMeasurementObject,
+                setterMethod,
+                measurement->azimuth_uncertainty_deg);
+    }
+
+    jmethodID usedInFixSetterMethod = env->GetMethodID(gpsMeasurementClass, "setUsedInFix", "(Z)V");
+    env->CallObjectMethod(
+            gpsMeasurementObject,
+            usedInFixSetterMethod,
+            (flags & GPS_MEASUREMENT_HAS_USED_IN_FIX) && measurement->used_in_fix);
+
+    return gpsMeasurementObject;
+}
+
+static jobjectArray translate_gps_measurements(JNIEnv* env, GpsData* data) {
+    size_t measurementCount = data->measurement_count;
+    if (measurementCount == 0) {
+        return NULL;
+    }
+
+    jclass gpsMeasurementClass = env->FindClass("android/location/GpsMeasurement");
+    jobjectArray gpsMeasurementArray = env->NewObjectArray(
+            measurementCount,
+            gpsMeasurementClass,
+            NULL /* initialElement */);
+
+    GpsMeasurement* gpsMeasurements = data->measurements;
+    for (uint16_t i = 0; i < measurementCount; ++i) {
+        jobject gpsMeasurement = translate_gps_measurement(
+                env,
+                &gpsMeasurements[i],
+                data->clock.time_ns);
+        env->SetObjectArrayElement(gpsMeasurementArray, i, gpsMeasurement);
+        env->DeleteLocalRef(gpsMeasurement);
+    }
+
+    env->DeleteLocalRef(gpsMeasurementClass);
+    return gpsMeasurementArray;
+}
+
+static void measurement_callback(GpsData* data) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (data == NULL) {
+        ALOGE("Invalid data provided to gps_measurement_callback");
+        return;
+    }
+
+    if (data->size == sizeof(GpsData)) {
+        jobject gpsClock = translate_gps_clock(env, &data->clock);
+        jobjectArray measurementArray = translate_gps_measurements(env, data);
+
+        jclass gpsMeasurementsEventClass = env->FindClass("android/location/GpsMeasurementsEvent");
+        jmethodID gpsMeasurementsEventCtor = env->GetMethodID(
+                gpsMeasurementsEventClass,
+                "<init>",
+                "(Landroid/location/GpsClock;[Landroid/location/GpsMeasurement;)V");
+
+        jobject gpsMeasurementsEvent = env->NewObject(
+                gpsMeasurementsEventClass,
+                gpsMeasurementsEventCtor,
+                gpsClock,
+                measurementArray);
+
+        env->CallVoidMethod(mCallbacksObj, method_reportMeasurementData, gpsMeasurementsEvent);
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    } else {
+        ALOGE("Invalid GpsData size found in gps_measurement_callback, size=%d", data->size);
+        return;
+    }
+}
+
+GpsMeasurementCallbacks sGpsMeasurementCallbacks = {
+    sizeof(GpsMeasurementCallbacks),
+    measurement_callback,
+};
+
+static jboolean android_location_GpsLocationProvider_is_measurement_supported(
+        JNIEnv* env,
+        jobject obj) {
+    if (sGpsMeasurementInterface != NULL) {
+        return JNI_TRUE;
+    }
+    return JNI_FALSE;
+}
+
+static jboolean android_location_GpsLocationProvider_start_measurement_collection(
+        JNIEnv* env,
+        jobject obj) {
+    if (sGpsMeasurementInterface == NULL) {
+        ALOGE("Measurement interface is not available.");
+        return JNI_FALSE;
+    }
+
+    int result = sGpsMeasurementInterface->init(&sGpsMeasurementCallbacks);
+    if (result != GPS_GEOFENCE_OPERATION_SUCCESS) {
+        ALOGE("An error has been found on GpsMeasurementInterface::init, status=%d", result);
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+static jboolean android_location_GpsLocationProvider_stop_measurement_collection(
+        JNIEnv* env,
+        jobject obj) {
+    if (sGpsMeasurementInterface == NULL) {
+        ALOGE("Measurement interface not available");
+        return JNI_FALSE;
+    }
+
+    sGpsMeasurementInterface->close();
+    return JNI_TRUE;
+}
+
 static JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
     {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
     {"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported},
     {"native_init", "()Z", (void*)android_location_GpsLocationProvider_init},
     {"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup},
-    {"native_set_position_mode", "(IIIII)Z", (void*)android_location_GpsLocationProvider_set_position_mode},
+    {"native_set_position_mode",
+            "(IIIII)Z",
+            (void*)android_location_GpsLocationProvider_set_position_mode},
     {"native_start", "()Z", (void*)android_location_GpsLocationProvider_start},
     {"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop},
-    {"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data},
-    {"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status},
+    {"native_delete_aiding_data",
+            "(I)V",
+            (void*)android_location_GpsLocationProvider_delete_aiding_data},
+    {"native_read_sv_status",
+            "([I[F[F[F[I)I",
+            (void*)android_location_GpsLocationProvider_read_sv_status},
     {"native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea},
     {"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time},
-    {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location},
+    {"native_inject_location",
+            "(DDF)V",
+            (void*)android_location_GpsLocationProvider_inject_location},
     {"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra},
-    {"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data},
-    {"native_agps_data_conn_open", "(Ljava/lang/String;I)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open},
-    {"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed},
-    {"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
-    {"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id},
-    {"native_agps_set_ref_location_cellid","(IIIII)V",(void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid},
-    {"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server},
-    {"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response},
-    {"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message},
-    {"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
-    {"native_update_network_state", "(ZIZZLjava/lang/String;Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state },
-    {"native_is_geofence_supported", "()Z", (void*) android_location_GpsLocationProvider_is_geofence_supported},
-    {"native_add_geofence", "(IDDDIIII)Z", (void *)android_location_GpsLocationProvider_add_geofence},
-    {"native_remove_geofence", "(I)Z", (void *)android_location_GpsLocationProvider_remove_geofence},
+    {"native_inject_xtra_data",
+            "([BI)V",
+            (void*)android_location_GpsLocationProvider_inject_xtra_data},
+    {"native_agps_data_conn_open",
+            "(Ljava/lang/String;I)V",
+            (void*)android_location_GpsLocationProvider_agps_data_conn_open},
+    {"native_agps_data_conn_closed",
+            "()V",
+            (void*)android_location_GpsLocationProvider_agps_data_conn_closed},
+    {"native_agps_data_conn_failed",
+            "()V",
+            (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
+    {"native_agps_set_id",
+            "(ILjava/lang/String;)V",
+            (void*)android_location_GpsLocationProvider_agps_set_id},
+    {"native_agps_set_ref_location_cellid",
+            "(IIIII)V",
+            (void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid},
+    {"native_set_agps_server",
+            "(ILjava/lang/String;I)V",
+            (void*)android_location_GpsLocationProvider_set_agps_server},
+    {"native_send_ni_response",
+            "(II)V",
+            (void*)android_location_GpsLocationProvider_send_ni_response},
+    {"native_agps_ni_message",
+            "([BI)V",
+            (void *)android_location_GpsLocationProvider_agps_send_ni_message},
+    {"native_get_internal_state",
+            "()Ljava/lang/String;",
+            (void*)android_location_GpsLocationProvider_get_internal_state},
+    {"native_update_network_state",
+            "(ZIZZLjava/lang/String;Ljava/lang/String;)V",
+            (void*)android_location_GpsLocationProvider_update_network_state },
+    {"native_is_geofence_supported",
+            "()Z",
+            (void*) android_location_GpsLocationProvider_is_geofence_supported},
+    {"native_add_geofence",
+            "(IDDDIIII)Z",
+            (void *)android_location_GpsLocationProvider_add_geofence},
+    {"native_remove_geofence",
+            "(I)Z",
+            (void *)android_location_GpsLocationProvider_remove_geofence},
     {"native_pause_geofence", "(I)Z", (void *)android_location_GpsLocationProvider_pause_geofence},
-    {"native_resume_geofence", "(II)Z", (void *)android_location_GpsLocationProvider_resume_geofence}
+    {"native_resume_geofence",
+            "(II)Z",
+            (void *)android_location_GpsLocationProvider_resume_geofence},
+    {"native_is_measurement_supported",
+            "()Z",
+            (void*) android_location_GpsLocationProvider_is_measurement_supported},
+    {"native_start_measurement_collection",
+            "()Z",
+            (void*) android_location_GpsLocationProvider_start_measurement_collection},
+    {"native_stop_measurement_collection",
+            "()Z",
+            (void*) android_location_GpsLocationProvider_stop_measurement_collection}
 };
 
 int register_android_server_location_GpsLocationProvider(JNIEnv* env)
 {
-    return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods));
+    return jniRegisterNativeMethods(
+            env,
+            "com/android/server/location/GpsLocationProvider",
+            sMethods,
+            NELEM(sMethods));
 }
 
 } /* namespace android */
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e0ebd54..8d38827 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -910,7 +910,9 @@
 
             mSystemServiceManager.startService(MediaSessionService.class);
 
-            mSystemServiceManager.startService(HdmiControlService.class);
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
+                mSystemServiceManager.startService(HdmiControlService.class);
+            }
 
             mSystemServiceManager.startService(TvInputManagerService.class);
 
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 6822ee3..5233297 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -39,6 +39,7 @@
 import android.os.SystemProperties;
 import android.os.UEventObserver;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
 import android.provider.Settings;
@@ -655,6 +656,17 @@
                     }
                     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");
+                        break;
+                    }
+
                     final boolean mtpActive =
                             containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
                             || containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
@@ -663,7 +675,6 @@
                         setUsbConfig("none");
                         setUsbConfig(mCurrentFunctions);
                     }
-                    mCurrentUser = msg.arg1;
                     break;
                 }
             }
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index b6ae192..fd83f92 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -248,6 +249,15 @@
     @Override
     public void setCurrentFunction(String function, boolean makeDefault) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        // If attempt to change USB function while file transfer is restricted, ensure that
+        // the current function is set to "none", and return.
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        if (userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
+            if (mDeviceManager != null) mDeviceManager.setCurrentFunctions("none", false);
+            return;
+        }
+
         if (mDeviceManager != null) {
             mDeviceManager.setCurrentFunctions(function, makeDefault);
         } else {
diff --git a/telecomm/java/android/telecomm/CallCapabilities.java b/telecomm/java/android/telecomm/CallCapabilities.java
index 924526e..e64fe80 100644
--- a/telecomm/java/android/telecomm/CallCapabilities.java
+++ b/telecomm/java/android/telecomm/CallCapabilities.java
@@ -48,8 +48,12 @@
     /** Remote device supports video telephony. */
     public static final int SUPPORTS_VT_REMOTE = 0x00000200;
 
+    public static final int VoLTE = 0x00000400;
+
+    public static final int VoWIFI = 0x00000800;
+
     public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL
-            | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE;
+            | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE | VoLTE | VoWIFI;
 
     public static String toString(int capabilities) {
         StringBuilder builder = new StringBuilder();
@@ -84,6 +88,12 @@
         if ((capabilities & SUPPORTS_VT_REMOTE) != 0) {
             builder.append(" SUPPORTS_VT_REMOTE");
         }
+        if ((capabilities & VoLTE) != 0) {
+            builder.append(" VoLTE");
+        }
+        if ((capabilities & VoWIFI) != 0) {
+            builder.append(" VoWIFI");
+        }
         builder.append("]");
         return builder.toString();
     }
diff --git a/telecomm/java/android/telecomm/CallFeatures.java b/telecomm/java/android/telecomm/CallFeatures.java
deleted file mode 100644
index 076d25a..0000000
--- a/telecomm/java/android/telecomm/CallFeatures.java
+++ /dev/null
@@ -1,29 +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;
-
-/**
- * Defines features of a call.  These features represent properties of a call for which it may be
- * desirable to display an in-call indicator.
- */
-public final class CallFeatures {
-    private CallFeatures() {};
-
-    public static final int NONE = 0x0;
-    public static final int VoLTE = 0x1;
-    public static final int VoWIFI = 0x2;
-}
\ No newline at end of file
diff --git a/telecomm/java/android/telecomm/CallNumberPresentation.java b/telecomm/java/android/telecomm/CallPropertyPresentation.java
similarity index 63%
rename from telecomm/java/android/telecomm/CallNumberPresentation.java
rename to telecomm/java/android/telecomm/CallPropertyPresentation.java
index 6cd22f8..350980c 100644
--- a/telecomm/java/android/telecomm/CallNumberPresentation.java
+++ b/telecomm/java/android/telecomm/CallPropertyPresentation.java
@@ -16,17 +16,17 @@
 
 package android.telecomm;
 
-/** Defines how numbers are displayed in caller id. */
-public enum CallNumberPresentation {
-    /** Number is displayed normally. */
-    ALLOWED,
+/** Defines how numbers and names are displayed in caller id. */
+public class CallPropertyPresentation {
+    /** Property is displayed normally. */
+    public static final int ALLOWED = 0;
 
-    /** Number was blocked. */
-    RESTRICTED,
+    /** Property was blocked. */
+    public static final int RESTRICTED = 1;
 
     /** Presentation was not specified or is unknown. */
-    UNKNOWN,
+    public static final int UNKNOWN = 2;
 
-    /** Number should be displayed as a pay phone. */
-    PAYPHONE
+    /** Property should be displayed as a pay phone. */
+    public static final int PAYPHONE = 3;
 }
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 05b0062..fb820f0 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -32,62 +32,20 @@
 public abstract class Connection {
 
     /** @hide */
-    public interface Listener {
-        void onStateChanged(Connection c, int state);
-        void onFeaturesChanged(Connection c, int features);
-        void onHandleChanged(Connection c, Uri newHandle);
-        void onSignalChanged(Connection c, Bundle details);
-        void onDisconnected(Connection c, int cause, String message);
-        void onPostDialWait(Connection c, String remaining);
-        void onRequestingRingback(Connection c, boolean ringback);
-        void onDestroyed(Connection c);
-        void onCallCapabilitiesChanged(Connection c, int callCapabilities);
-        void onParentConnectionChanged(Connection c, Connection parent);
-        void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider);
-        void onSetAudioModeIsVoip(Connection c, boolean isVoip);
-        void onSetStatusHints(Connection c, StatusHints statusHints);
-    }
-
-    /** @hide */
-    public static class ListenerBase implements Listener {
-        @Override
+    public abstract static class Listener {
         public void onStateChanged(Connection c, int state) {}
-
-        /** {@inheritDoc} */
-        @Override
-        public void onFeaturesChanged(Connection c, int features) {}
-
-        @Override
-        public void onHandleChanged(Connection c, Uri newHandle) {}
-
-        @Override
+        public void onHandleChanged(Connection c, Uri newHandle, int presentation) {}
+        public void onCallerDisplayNameChanged(
+                Connection c, String callerDisplayName, int presentation) {}
         public void onSignalChanged(Connection c, Bundle details) {}
-
-        @Override
         public void onDisconnected(Connection c, int cause, String message) {}
-
-        @Override
-        public void onDestroyed(Connection c) {}
-
-        @Override
         public void onPostDialWait(Connection c, String remaining) {}
-
-        @Override
         public void onRequestingRingback(Connection c, boolean ringback) {}
-
-        @Override
+        public void onDestroyed(Connection c) {}
         public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {}
-
-        @Override
         public void onParentConnectionChanged(Connection c, Connection parent) {}
-
-        @Override
         public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {}
-
-        @Override
         public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {}
-
-        @Override
         public void onSetStatusHints(Connection c, StatusHints statusHints) {}
     }
 
@@ -106,9 +64,11 @@
     private final List<Connection> mChildConnections = new ArrayList<>();
 
     private int mState = State.NEW;
-    private int mFeatures = CallFeatures.NONE;
     private CallAudioState mCallAudioState;
     private Uri mHandle;
+    private int mHandlePresentation;
+    private String mCallerDisplayName;
+    private int mCallerDisplayNamePresentation;
     private boolean mRequestingRingback = false;
     private int mCallCapabilities;
     private Connection mParentConnection;
@@ -122,21 +82,35 @@
     protected Connection() {}
 
     /**
-     * The handle (e.g., phone number) to which this Connection is currently communicating.
-     *
-     * IMPORTANT: If an incoming connection has a phone number (or other handle) that the user
-     * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
-     * ConnectionService implementation MUST NOT reveal this phone number and MUST return
-     * {@code null} from this method.
-     *
-     * @return The handle (e.g., phone number) to which this Connection
-     *         is currently communicating.
+     * @return The handle (e.g., phone number) to which this Connection is currently communicating.
      */
     public final Uri getHandle() {
         return mHandle;
     }
 
     /**
+     * @return The {@link CallPropertyPresentation} which controls how the handle is shown.
+     */
+    public final int getHandlePresentation() {
+        return mHandlePresentation;
+    }
+
+    /**
+     * @return The caller display name (CNAP).
+     */
+    public final String getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    /**
+     * @return The {@link CallPropertyPresentation} which controls how the caller display name is
+     *         shown.
+     */
+    public final int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
+    /**
      * @return The state of this Connection.
      */
     public final int getState() {
@@ -144,14 +118,6 @@
     }
 
     /**
-     * @return The features of the call.  These are items for which the InCall UI may wish to
-     *         display a visual indicator.
-     */
-    public final int getFeatures() {
-        return mFeatures;
-    }
-
-    /**
      * @return The audio state of the call, describing how its audio is currently
      *         being routed by the system. This is {@code null} if this Connection
      *         does not directly know about its audio state.
@@ -287,31 +253,34 @@
     }
 
     /**
-     * Sets the value of the {@link #getHandle()} property and notifies listeners.
+     * Sets the value of the {@link #getHandle()} property.
      *
      * @param handle The new handle.
+     * @param presentation The {@link CallPropertyPresentation} which controls how the handle is
+     *         shown.
      */
-    public final void setHandle(Uri handle) {
+    public final void setHandle(Uri handle, int presentation) {
         Log.d(this, "setHandle %s", handle);
         mHandle = handle;
+        mHandlePresentation = presentation;
         for (Listener l : mListeners) {
-            l.onHandleChanged(this, handle);
+            l.onHandleChanged(this, handle, presentation);
         }
     }
 
     /**
-     * Set the features applicable to the connection.  These are items for which the InCall UI may
-     * wish to display a visual indicator.
-     * Features are defined in {@link android.telecomm.CallFeatures} and are passed in as a
-     * bit-mask.
+     * Sets the caller display name (CNAP).
      *
-     * @param features The features active.
+     * @param callerDisplayName The new display name.
+     * @param presentation The {@link CallPropertyPresentation} which controls how the name is
+     *         shown.
      */
-    public final void setFeatures(int features) {
-        Log.d(this, "setFeatures %d", features);
-        mFeatures = features;
+    public final void setCallerDisplayName(String callerDisplayName, int presentation) {
+        Log.d(this, "setCallerDisplayName %s", callerDisplayName);
+        mCallerDisplayName = callerDisplayName;
+        mCallerDisplayNamePresentation = presentation;
         for (Listener l : mListeners) {
-            l.onFeaturesChanged(this, mFeatures);
+            l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
         }
     }
 
@@ -428,8 +397,7 @@
     }
 
     /**
-     * Notifies this Connection and listeners of a change in the current signal levels
-     * for the underlying data transport.
+     * Sets the current signal levels for the underlying data transport.
      *
      * @param details A {@link android.os.Bundle} containing details of the current level.
      */
@@ -464,8 +432,7 @@
     }
 
     /**
-     * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
-     * has a new value.
+     * Notifies this Connection that the {@link #getCallAudioState()} property has a new value.
      *
      * @param state The new call audio state.
      */
@@ -534,6 +501,12 @@
     protected void onPostDialContinue(boolean proceed) {}
 
     /**
+     * Swap this call with a background call. This is used for calls that don't support hold,
+     * e.g. CDMA.
+     */
+    protected void onSwapWithBackgroundCall() {}
+
+    /**
      * TODO(santoscordon): Needs documentation.
      */
     protected void onChildrenChanged(List<Connection> children) {}
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index 28fea79..0db9e29 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -16,8 +16,8 @@
 
 package android.telecomm;
 
-import android.os.Bundle;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -32,20 +32,32 @@
     //         numbers that would satisfy the client's needs, in order of preference
     private final String mCallId;
     private final Uri mHandle;
+    private final int mHandlePresentation;
     private final Bundle mExtras;
     private final PhoneAccount mAccount;
     private final int mVideoState;
 
-    public ConnectionRequest(String callId, Uri handle, Bundle extras, int videoState) {
-        this(null, callId, handle, extras, videoState);
-    }
-
-    public ConnectionRequest(PhoneAccount account, String callId, Uri handle, Bundle extras,
+    /**
+     * @param account The account which should be used to place the call.
+     * @param callId An identifier for this call.
+     * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect.
+     * @param handlePresentation The {@link CallPropertyPresentation} which controls how the handle
+     *         is shown.
+     * @param extras Application-specific extra data.
+     * @param videoState Determines the video state for the connection.
+     */
+    public ConnectionRequest(
+            PhoneAccount account,
+            String callId,
+            Uri handle,
+            int handlePresentation,
+            Bundle extras,
             int videoState) {
+        mAccount = account;
         mCallId = callId;
         mHandle = handle;
+        mHandlePresentation = handlePresentation;
         mExtras = extras;
-        mAccount = account;
         mVideoState = videoState;
     }
 
@@ -65,6 +77,11 @@
     public Uri getHandle() { return mHandle; }
 
     /**
+     * The {@link CallPropertyPresentation} which controls how the handle is shown.
+     */
+    public int getHandlePresentation() { return mHandlePresentation; }
+
+    /**
      * Application-specific extra data. Used for passing back information from an incoming
      * call {@code Intent}, and for any proprietary extensions arranged between a client
      * and servant {@code ConnectionService} which agree on a vocabulary for such data.
@@ -96,11 +113,15 @@
             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(callId, handle, extras, videoState);
+                    return new ConnectionRequest(
+                            account, callId, handle, presentation, extras, videoState);
                 }
 
                 @Override
@@ -119,8 +140,10 @@
 
     @Override
     public void writeToParcel(Parcel destination, int flags) {
+        destination.writeParcelable(mAccount, 0);
         destination.writeString(mCallId);
         destination.writeParcelable(mHandle, 0);
+        destination.writeInt(mHandlePresentation);
         destination.writeParcelable(mExtras, 0);
         destination.writeInt(mVideoState);
     }
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index d0ad0cc..1966081 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -60,8 +60,9 @@
     private static final int MSG_STOP_DTMF_TONE = 12;
     private static final int MSG_CONFERENCE = 13;
     private static final int MSG_SPLIT_FROM_CONFERENCE = 14;
-    private static final int MSG_ON_POST_DIAL_CONTINUE = 15;
-    private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 16;
+    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 final Map<String, Connection> mConnectionById = new HashMap<>();
     private final Map<Connection, String> mIdByConnection = new HashMap<>();
@@ -179,6 +180,11 @@
         }
 
         @Override
+        public void swapWithBackgroundCall(String callId) {
+            mHandler.obtainMessage(MSG_SWAP_WITH_BACKGROUND_CALL, callId).sendToTarget();
+        }
+
+        @Override
         public void onPostDialContinue(String callId, boolean proceed) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = callId;
@@ -255,6 +261,9 @@
                 case MSG_SPLIT_FROM_CONFERENCE:
                     splitFromConference((String) msg.obj);
                     break;
+                case MSG_SWAP_WITH_BACKGROUND_CALL:
+                    swapWithBackgroundCall((String) msg.obj);
+                    break;
                 case MSG_ON_POST_DIAL_CONTINUE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
@@ -302,14 +311,6 @@
             }
         }
 
-        /** {@inheritDoc} */
-        @Override
-        public void onFeaturesChanged(Connection c, int features) {
-            String id = mIdByConnection.get(c);
-            Log.d(this, "Adapter set features %d", features);
-            mAdapter.setFeatures(id, features);
-        }
-
         @Override
         public void onDisconnected(Connection c, int cause, String message) {
             String id = mIdByConnection.get(c);
@@ -318,8 +319,16 @@
         }
 
         @Override
-        public void onHandleChanged(Connection c, Uri newHandle) {
-            // TODO: Unsupported yet
+        public void onHandleChanged(Connection c, Uri handle, int presentation) {
+            String id = mIdByConnection.get(c);
+            mAdapter.setHandle(id, handle, presentation);
+        }
+
+        @Override
+        public void onCallerDisplayNameChanged(
+                Connection c, String callerDisplayName, int presentation) {
+            String id = mIdByConnection.get(c);
+            mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
         }
 
         @Override
@@ -527,6 +536,11 @@
         // TODO(santoscordon): Find existing conference call and invoke split(connection).
     }
 
+    private void swapWithBackgroundCall(String callId) {
+        Log.d(this, "swapWithBackgroundCall(%s)", callId);
+        findConnectionForAction(callId, "swapWithBackgroundCall").onSwapWithBackgroundCall();
+    }
+
     private void onPostDialContinue(String callId, boolean proceed) {
         Log.d(this, "onPostDialContinue(%s)", callId);
         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
index b60e7c6..b90dec3 100644
--- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
@@ -17,6 +17,7 @@
 package android.telecomm;
 
 import android.content.ComponentName;
+import android.net.Uri;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
@@ -28,6 +29,7 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -61,9 +63,12 @@
     /** ${inheritDoc} */
     @Override
     public void binderDied() {
-        for (IConnectionServiceAdapter adapter : mAdapters) {
+        Iterator<IConnectionServiceAdapter> it = mAdapters.iterator();
+        while (it.hasNext()) {
+            IConnectionServiceAdapter adapter = it.next();
             if (!adapter.asBinder().isBinderAlive()) {
-                removeAdapter(adapter);
+                it.remove();
+                adapter.asBinder().unlinkToDeath(this, 0);
             }
         }
     }
@@ -338,20 +343,20 @@
         }
     }
 
-    /**
-    * Set the features associated with the given call.
-    * Features are defined in {@link android.telecomm.CallFeatures} and are passed in as a
-    * bit-mask.
-    *
-    * @param callId The unique ID of the call to set features for.
-    * @param features The features.
-    */
-    void setFeatures(String callId, int features) {
-        Log.v(this, "setFeatures: %d", features);
+    void setHandle(String callId, Uri handle, int presentation) {
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.setFeatures(callId, features);
-            } catch (RemoteException ignored) {
+                adapter.setHandle(callId, handle, presentation);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void setCallerDisplayName(String callId, String callerDisplayName, int presentation) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setCallerDisplayName(callId, callerDisplayName, presentation);
+            } catch (RemoteException e) {
             }
         }
     }
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index aef05fd..d8293a5 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -226,4 +226,17 @@
         } catch (RemoteException ignored) {
         }
     }
+
+    /**
+     * Swap this call with a background call. This is used for calls that don't support hold,
+     * e.g. CDMA.
+     *
+     * @param callId The unique ID of the call.
+     */
+    public void swapWithBackgroundCall(String callId) {
+        try {
+            mAdapter.swapWithBackgroundCall(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
 }
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 5396688..7c35020 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -40,6 +40,9 @@
     private final int mCapabilities;
     private final long mConnectTimeMillis;
     private final Uri mHandle;
+    private final int mHandlePresentation;
+    private final String mCallerDisplayName;
+    private final int mCallerDisplayNamePresentation;
     private final GatewayInfo mGatewayInfo;
     private final PhoneAccount mAccount;
     private final CallServiceDescriptor mCurrentCallServiceDescriptor;
@@ -47,7 +50,6 @@
     private RemoteCallVideoProvider mRemoteCallVideoProvider;
     private final String mParentCallId;
     private final List<String> mChildCallIds;
-    private final int mFeatures;
     private final StatusHints mStatusHints;
 
     /** @hide */
@@ -60,13 +62,15 @@
             int capabilities,
             long connectTimeMillis,
             Uri handle,
+            int handlePresentation,
+            String callerDisplayName,
+            int callerDisplayNamePresentation,
             GatewayInfo gatewayInfo,
             PhoneAccount account,
             CallServiceDescriptor descriptor,
             ICallVideoProvider callVideoProvider,
             String parentCallId,
             List<String> childCallIds,
-            int features,
             StatusHints statusHints) {
         mId = id;
         mState = state;
@@ -76,13 +80,15 @@
         mCapabilities = capabilities;
         mConnectTimeMillis = connectTimeMillis;
         mHandle = handle;
+        mHandlePresentation = handlePresentation;
+        mCallerDisplayName = callerDisplayName;
+        mCallerDisplayNamePresentation = callerDisplayNamePresentation;
         mGatewayInfo = gatewayInfo;
         mAccount = account;
         mCurrentCallServiceDescriptor = descriptor;
         mCallVideoProvider = callVideoProvider;
         mParentCallId = parentCallId;
         mChildCallIds = childCallIds;
-        mFeatures = features;
         mStatusHints = statusHints;
     }
 
@@ -134,6 +140,21 @@
         return mHandle;
     }
 
+    /** The {@link CallPropertyPresentation} which controls how the handle is shown. */
+    public int getHandlePresentation() {
+        return mHandlePresentation;
+    }
+
+    /** The endpoint to which the call is connected. */
+    public String getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    /** The {@link CallPropertyPresentation} which controls how the caller display name is shown. */
+    public int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
     /** Gateway information for the call. */
     public GatewayInfo getGatewayInfo() {
         return mGatewayInfo;
@@ -183,15 +204,6 @@
     }
 
     /**
-     * The features of this call (e.g. VoLTE, VoWIFI).
-     *
-     * @return Features.
-     */
-    public int getFeatures() {
-        return mFeatures;
-    }
-
-    /**
      * The status label and icon.
      *
      * @return Status hints.
@@ -215,6 +227,9 @@
             int capabilities = source.readInt();
             long connectTimeMillis = source.readLong();
             Uri handle = source.readParcelable(classLoader);
+            int handlePresentation = source.readInt();
+            String callerDisplayName = source.readString();
+            int callerDisplayNamePresentation = source.readInt();
             GatewayInfo gatewayInfo = source.readParcelable(classLoader);
             PhoneAccount account = source.readParcelable(classLoader);
             CallServiceDescriptor descriptor = source.readParcelable(classLoader);
@@ -223,11 +238,11 @@
             String parentCallId = source.readString();
             List<String> childCallIds = new ArrayList<>();
             source.readList(childCallIds, classLoader);
-            int features = source.readInt();
             StatusHints statusHints = source.readParcelable(classLoader);
             return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg,
-                    cannedSmsResponses, capabilities, connectTimeMillis, handle, gatewayInfo,
-                    account, descriptor, callVideoProvider, parentCallId, childCallIds, features,
+                    cannedSmsResponses, capabilities, connectTimeMillis, handle, handlePresentation,
+                    callerDisplayName, callerDisplayNamePresentation, gatewayInfo,
+                    account, descriptor, callVideoProvider, parentCallId, childCallIds,
                     statusHints);
         }
 
@@ -254,6 +269,9 @@
         destination.writeInt(mCapabilities);
         destination.writeLong(mConnectTimeMillis);
         destination.writeParcelable(mHandle, 0);
+        destination.writeInt(mHandlePresentation);
+        destination.writeString(mCallerDisplayName);
+        destination.writeInt(mCallerDisplayNamePresentation);
         destination.writeParcelable(mGatewayInfo, 0);
         destination.writeParcelable(mAccount, 0);
         destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
@@ -261,7 +279,6 @@
                 mCallVideoProvider != null ? mCallVideoProvider.asBinder() : null);
         destination.writeString(mParentCallId);
         destination.writeList(mChildCallIds);
-        destination.writeInt(mFeatures);
         destination.writeParcelable(mStatusHints, 0);
     }
 
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
index 8ef283a..3475305 100644
--- a/telecomm/java/android/telecomm/RemoteConnection.java
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -37,9 +37,11 @@
         void onRequestingRingback(RemoteConnection connection, boolean ringback);
         void onCallCapabilitiesChanged(RemoteConnection connection, int callCapabilities);
         void onPostDialWait(RemoteConnection connection, String remainingDigits);
-        void onFeaturesChanged(RemoteConnection connection, int features);
-        void onSetAudioModeIsVoip(RemoteConnection connection, boolean isVoip);
-        void onSetStatusHints(RemoteConnection connection, StatusHints statusHints);
+        void onAudioModeIsVoipChanged(RemoteConnection connection, boolean isVoip);
+        void onStatusHintsChanged(RemoteConnection connection, StatusHints statusHints);
+        void onHandleChanged(RemoteConnection connection, Uri handle, int presentation);
+        void onCallerDisplayNameChanged(
+                RemoteConnection connection, String callerDisplayName, int presentation);
         void onDestroyed(RemoteConnection connection);
     }
 
@@ -53,9 +55,12 @@
     private boolean mRequestingRingback;
     private boolean mConnected;
     private int mCallCapabilities;
-    private int mFeatures;
     private boolean mAudioModeIsVoip;
     private StatusHints mStatusHints;
+    private Uri mHandle;
+    private int mHandlePresentation;
+    private String mCallerDisplayName;
+    private int mCallerDisplayNamePresentation;
 
     /**
      * @hide
@@ -91,10 +96,6 @@
         return mCallCapabilities;
     }
 
-    public int getFeatures() {
-        return mFeatures;
-    }
-
     public boolean getAudioModeIsVoip() {
         return mAudioModeIsVoip;
     }
@@ -103,6 +104,22 @@
         return mStatusHints;
     }
 
+    public Uri getHandle() {
+        return mHandle;
+    }
+
+    public int getHandlePresentation() {
+        return mHandlePresentation;
+    }
+
+    public String getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    public int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
     public void abort() {
         try {
             if (mConnected) {
@@ -184,6 +201,15 @@
         }
     }
 
+    public void swapWithBackgroundCall() {
+        try {
+            if (mConnected) {
+                mConnectionService.swapWithBackgroundCall(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
     public void setAudioState(CallAudioState state) {
         try {
             if (mConnected) {
@@ -271,21 +297,11 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    void setFeatures(int features) {
-        mFeatures = features;
-        for (Listener l : mListeners) {
-            l.onFeaturesChanged(this, features);
-        }
-    }
-
     /** @hide */
     void setAudioModeIsVoip(boolean isVoip) {
         mAudioModeIsVoip = isVoip;
         for (Listener l : mListeners) {
-            l.onSetAudioModeIsVoip(this, isVoip);
+            l.onAudioModeIsVoipChanged(this, isVoip);
         }
     }
 
@@ -293,7 +309,25 @@
     void setStatusHints(StatusHints statusHints) {
         mStatusHints = statusHints;
         for (Listener l : mListeners) {
-            l.onSetStatusHints(this, statusHints);
+            l.onStatusHintsChanged(this, statusHints);
+        }
+    }
+
+    /** @hide */
+    void setHandle(Uri handle, int presentation) {
+        mHandle = handle;
+        mHandlePresentation = presentation;
+        for (Listener l : mListeners) {
+            l.onHandleChanged(this, handle, presentation);
+        }
+    }
+
+    /** @hide */
+    void setCallerDisplayName(String callerDisplayName, int presentation) {
+        mCallerDisplayName = callerDisplayName;
+        mCallerDisplayNamePresentation = presentation;
+        for (Listener l : mListeners) {
+            l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
         }
     }
 }
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
index c22005b..a436af2 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionService.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -50,13 +50,11 @@
 
     private final IConnectionServiceAdapter mAdapter = new IConnectionServiceAdapter.Stub() {
 
-        /** ${inheritDoc} */
         @Override
         public void notifyIncomingCall(ConnectionRequest request) {
             Log.w(this, "notifyIncomingCall not implemented in Remote connection");
         }
 
-        /** ${inheritDoc} */
         @Override
         public void handleSuccessfulOutgoingCall(ConnectionRequest request) {
             if (isPendingConnection(request.getCallId())) {
@@ -66,7 +64,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void handleFailedOutgoingCall(
                 ConnectionRequest request, int errorCode, String errorMessage) {
@@ -77,7 +74,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void cancelOutgoingCall(ConnectionRequest request) {
             if (isPendingConnection(request.getCallId())) {
@@ -87,7 +83,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setActive(String connectionId) {
             if (isCurrentConnection(connectionId)) {
@@ -95,7 +90,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setRinging(String connectionId) {
             if (isCurrentConnection(connectionId)) {
@@ -103,13 +97,12 @@
             }
         }
 
-        /** ${inheritDoc} */
+        @Override
         public void setCallVideoProvider(
                 String connectionId, ICallVideoProvider callVideoProvider) {
             // not supported for remote connections.
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setDialing(String connectionId) {
             if (isCurrentConnection(connectionId)) {
@@ -117,7 +110,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setDisconnected(
                 String connectionId, int disconnectCause, String disconnectMessage) {
@@ -126,7 +118,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setOnHold(String connectionId) {
             if (isCurrentConnection(connectionId)) {
@@ -134,7 +125,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setRequestingRingback(String connectionId, boolean isRequestingRingback) {
             if (isCurrentConnection(connectionId)) {
@@ -142,7 +132,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setCallCapabilities(String connectionId, int callCapabilities) {
             if (isCurrentConnection(connectionId)) {
@@ -150,19 +139,16 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void setIsConferenced(String connectionId, String conferenceConnectionId) {
             // not supported for remote connections.
         }
 
-        /** ${inheritDoc} */
         @Override
         public void addConferenceCall(String connectionId) {
             // not supported for remote connections.
         }
 
-        /** ${inheritDoc} */
         @Override
         public void removeCall(String connectionId) {
             if (isCurrentConnection(connectionId)) {
@@ -170,7 +156,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void onPostDialWait(String connectionId, String remainingDigits) {
             if (isCurrentConnection(connectionId)) {
@@ -178,7 +163,6 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
             try {
@@ -188,15 +172,6 @@
             }
         }
 
-        /** ${inheritDoc} */
-        @Override
-        public void setFeatures(String connectionId, int features) {
-            if (isCurrentConnection(connectionId)) {
-                mConnection.setFeatures(features);
-            }
-        }
-
-        /** ${inheritDoc} */
         @Override
         public final void setAudioModeIsVoip(String connectionId, boolean isVoip) {
             if (isCurrentConnection(connectionId)) {
@@ -204,13 +179,27 @@
             }
         }
 
-        /** ${inheritDoc} */
         @Override
         public final void setStatusHints(String connectionId, StatusHints statusHints) {
             if (isCurrentConnection(connectionId)) {
                 mConnection.setStatusHints(statusHints);
             }
         }
+
+        @Override
+        public final void setHandle(String connectionId, Uri handle, int presentation) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setHandle(handle, presentation);
+            }
+        }
+
+        @Override
+        public final void setCallerDisplayName(
+                String connectionId, String callerDisplayName, int presentation) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setCallerDisplayName(callerDisplayName, presentation);
+            }
+        }
     };
 
     RemoteConnectionService(ComponentName componentName, IConnectionService connectionService)
@@ -246,8 +235,13 @@
 
         if (mConnectionId == null) {
             String id = UUID.randomUUID().toString();
-            ConnectionRequest newRequest = new ConnectionRequest(request.getAccount(), id,
-                    request.getHandle(), request.getExtras(), request.getVideoState());
+            ConnectionRequest newRequest = new ConnectionRequest(
+                    request.getAccount(),
+                    id,
+                    request.getHandle(),
+                    request.getHandlePresentation(),
+                    request.getExtras(),
+                    request.getVideoState());
             try {
                 mConnectionService.call(newRequest);
                 mConnectionId = id;
diff --git a/telecomm/java/android/telecomm/StatusHints.java b/telecomm/java/android/telecomm/StatusHints.java
index 354dc36..5e64bff 100644
--- a/telecomm/java/android/telecomm/StatusHints.java
+++ b/telecomm/java/android/telecomm/StatusHints.java
@@ -21,6 +21,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.DisplayMetrics;
@@ -35,11 +36,13 @@
     private final ComponentName mComponentName;
     private final String mLabel;
     private final int mIconId;
+    private final Bundle mExtras;
 
-    public StatusHints(ComponentName componentName, String label, int iconId) {
+    public StatusHints(ComponentName componentName, String label, int iconId, Bundle extras) {
         mComponentName = componentName;
         mLabel = label;
         mIconId = iconId;
+        mExtras = extras;
     }
 
     /**
@@ -70,6 +73,13 @@
         return getIcon(context, mIconId);
     }
 
+    /**
+     * @return Extra data used to display status.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -80,6 +90,7 @@
         out.writeParcelable(mComponentName, flags);
         out.writeString(mLabel);
         out.writeInt(mIconId);
+        out.writeParcelable(mExtras, 0);
     }
 
     public static final Creator<StatusHints> CREATOR
@@ -97,6 +108,7 @@
         mComponentName = in.readParcelable(getClass().getClassLoader());
         mLabel = in.readString();
         mIconId = in.readInt();
+        mExtras = (Bundle) in.readParcelable(getClass().getClassLoader());
     }
 
     private Drawable getIcon(Context context, int resId) {
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
index c8ce4c6..16d2edf 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
@@ -58,6 +58,8 @@
 
     void splitFromConference(String callId);
 
+    void swapWithBackgroundCall(String callId);
+
     void onPostDialContinue(String callId, boolean proceed);
 
     void onPhoneAccountClicked(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
index e724bfb..bc67eab 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.telecomm;
 
+import android.net.Uri;
 import android.telecomm.ConnectionRequest;
 import android.telecomm.StatusHints;
 
@@ -64,9 +65,11 @@
 
     void setCallVideoProvider(String callId, ICallVideoProvider callVideoProvider);
 
-    void setFeatures(String callId, int features);
-
     void setAudioModeIsVoip(String callId, boolean isVoip);
 
     void setStatusHints(String callId, in StatusHints statusHints);
+
+    void setHandle(String callId, in Uri handle, int presentation);
+
+    void setCallerDisplayName(String callId, String callerDisplayName, int presentation);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
index 7f3767c..ce0309f 100644
--- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
@@ -51,4 +51,6 @@
     void conference(String callId);
 
     void splitFromConference(String callId);
+
+    void swapWithBackgroundCall(String callId);
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 15320e6..1c84741 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1695,6 +1695,15 @@
      */
     /** {@hide} */
     public String getLine1Number(long subId) {
+        String number = null;
+        try {
+            number = getITelephony().getLine1NumberForDisplay(subId);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        if (number != null) {
+            return number;
+        }
         try {
             return getSubscriberInfo().getLine1NumberUsingSubId(subId);
         } catch (RemoteException ex) {
@@ -1706,6 +1715,47 @@
     }
 
     /**
+     * Set the phone number string and its alphatag for line 1 for display
+     * purpose only, for example, displayed in Phone Status. It won't change
+     * the actual MSISDN/MDN. This setting won't be persisted during power cycle
+     * and it should be set again after reboot.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @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);
+    }
+
+    /**
+     * Set the phone number string and its alphatag for line 1 for display
+     * purpose only, for example, displayed in Phone Status. It won't change
+     * the actual MSISDN/MDN. This setting won't be persisted during power cycle
+     * and it should be set again after reboot.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @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 {
+            getITelephony().setLine1NumberForDisplay(subId, alphaTag, number);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+    }
+
+    /**
      * Returns the alphabetic identifier associated with the line 1 number.
      * Return null if it is unavailable.
      * <p>
@@ -1730,6 +1780,15 @@
      */
     /** {@hide} */
     public String getLine1AlphaTag(long subId) {
+        String alphaTag = null;
+        try {
+            alphaTag = getITelephony().getLine1AlphaTagForDisplay(subId);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        if (alphaTag != null) {
+            return alphaTag;
+        }
         try {
             return getSubscriberInfo().getLine1AlphaTagUsingSubId(subId);
         } catch (RemoteException ex) {
@@ -2870,6 +2929,19 @@
 
     /** @hide */
     @SystemApi
+    public int checkCarrierPrivilegesForPackage(String pkgname) {
+        try {
+            return getITelephony().checkCarrierPrivilegesForPackage(pkgname);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "hasCarrierPrivileges RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "hasCarrierPrivileges NPE", ex);
+        }
+        return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+    }
+
+    /** @hide */
+    @SystemApi
     public void dial(String number) {
         try {
             getITelephony().dial(number);
@@ -3171,4 +3243,73 @@
             Log.e(TAG, "Error calling ITelephony#setSystemDefault", e);
         }
     }
+
+    /**
+     * Set whether Android should display a simplified Mobile Network Settings UI.
+     * The setting won't be persisted during power cycle.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @param enable true means enabling the simplified UI.
+     *
+     * @hide
+     */
+    public void enableSimplifiedNetworkSettings(boolean enable) {
+        enableSimplifiedNetworkSettings(getDefaultSubscription(), enable);
+    }
+
+    /**
+     * Set whether Android should display a simplified Mobile Network Settings UI.
+     * The setting won't be persisted during power cycle.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @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 {
+            getITelephony().enableSimplifiedNetworkSettings(subId, enable);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+    }
+
+    /**
+     * Get whether a simplified Mobile Network Settings UI is enabled.
+     * <p>
+     * Requires Permission:
+     *   {@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());
+    }
+
+    /**
+     * Get whether a simplified Mobile Network Settings UI is enabled.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @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 {
+            return getITelephony().getSimplifiedNetworkSettingsEnabled(subId);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return false;
+    }
 }
diff --git a/telephony/java/com/android/ims/ImsConfigListener.aidl b/telephony/java/com/android/ims/ImsConfigListener.aidl
new file mode 100644
index 0000000..e827774
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsConfigListener.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+/**
+ * Used by IMS config client to monitor the config operation results.
+ * {@hide}
+ */
+oneway interface ImsConfigListener {
+    /**
+     * Notifies client the value of the get operation result on the feature config item.
+     * The arguments are the same as passed to com.android.ims.ImsConfig#getFeatureValue.
+     *
+     * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+     * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param value. as defined in com.android.ims.ImsConfig#FeatureValueConstants.
+     * @param status. as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+     * @return void.
+     */
+    void onGetFeatureResponse(int feature, int network, int value, int status);
+
+    /**
+     * Notifies client the set value operation result for feature config item.
+     * Used by clients that need to be notified the set operation result.
+     * The arguments are the same as passed to com.android.ims.ImsConfig#setFeatureValue.
+     * The arguments are repeated in the callback to enable the listener to understand
+     * which configuration attempt failed.
+     *
+     * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+     * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param value. as defined in com.android.ims.ImsConfig#FeatureValueConstants.
+     * @param status. as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+     *
+     * @return void.
+     */
+    void onSetFeatureResponse(int feature, int network, int value, int status);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsConfig.aidl b/telephony/java/com/android/ims/internal/IImsConfig.aidl
new file mode 100644
index 0000000..e8d921e
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsConfig.aidl
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.ims.internal;
+
+import com.android.ims.ImsConfigListener;
+
+/**
+ * Provides APIs to get/set the IMS service capability/parameters.
+ * The parameters can be configured by operator and/or user.
+ * We define 4 storage locations for the IMS config items:
+ * 1) Default config:For factory out device or device after factory data reset,
+ * the default config is used to build the initial state of the master config value.
+ * 2) Provisioned value: as the parameters provisioned by operator need to be preserved
+ * across FDR(factory data reset)/BOTA(over the air software upgrade), the operator
+ * provisioned items should be stored in memory location preserved across FDR/BOTA.
+ * 3) Master value: as the provisioned value can override the user setting,
+ * and the master config are used by IMS stack. They should be stored in the
+ * storage based on IMS vendor implementations.
+ * 4) User setting: For items can be changed by both user/operator, the user
+ * setting should take effect in some cases. So the user setting should be stored in
+ * database like setting.db.
+ *
+ * Priority consideration if both operator/user can config the same item:
+ * 1)  For feature config items, the master value is obtained from the provisioned value
+ * masks with the user setting. Specifically the provisioned values overrides
+ * the user setting if feature is provisioned off. Otherwise, user setting takes
+ * effect.
+ * 2) For non-feature config item: to be implemented based on cases.
+ * Special cases considered as below:
+ * 1) Factory out device, the master configuration is built from default config.
+ * 2) For Factory data reset/SW upgrade device, the master config is built by
+ * taking provisioned value overriding default config.
+ * {@hide}
+ */
+interface IImsConfig {
+    /**
+     * Gets the value for ims service/capabilities parameters from the master
+     * value storage. Synchronous blocking call.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @return value in Integer format.
+     */
+    int getMasterValue(int item);
+
+    /**
+     * Gets the value for ims service/capabilities parameters from the master
+     * value storage. Synchronous blocking call.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @return value in String format.
+     */
+    String getMasterStringValue(int item);
+
+    /**
+     * Sets the value for IMS service/capabilities parameters by the operator device
+     * management entity. It sets the config item value in the provisioned storage
+     * from which the master value is derived. Synchronous blocking call.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @param value in Integer format.
+     * @return void.
+     */
+    void setProvisionedValue(int item, int value);
+
+    /**
+     * Sets the value for IMS service/capabilities parameters by the operator device
+     * management entity. It sets the config item value in the provisioned storage
+     * from which the master value is derived.  Synchronous blocking call.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @param value in String format.
+     * @return void.
+     */
+    void setProvisionedStringValue(int item, String value);
+
+    /**
+     * Gets the value of the specified IMS feature item for specified network type.
+     * This operation gets the feature config value from the master storage (i.e. final
+     * value). Asynchronous non-blocking call.
+     *
+     * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+     * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param listener. feature value returned asynchronously through listener.
+     * @return void
+     */
+    oneway void getFeatureValue(int feature, int network, ImsConfigListener listener);
+
+    /**
+     * Sets the value for IMS feature item for specified network type.
+     * This operation stores the user setting in setting db from which master db
+     * is dervied.
+     *
+     * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+     * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param value. as defined in com.android.ims.ImsConfig#FeatureValueConstants.
+     * @param listener, provided if caller needs to be notified for set result.
+     * @return void
+     */
+    oneway void setFeatureValue(int feature, int network, int value, ImsConfigListener listener);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
index bac5651..d992124 100644
--- a/telephony/java/com/android/ims/internal/IImsService.aidl
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -23,6 +23,7 @@
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsCallSessionListener;
 import com.android.ims.internal.IImsUt;
+import com.android.ims.internal.IImsConfig;
 
 /**
  * {@hide}
@@ -45,4 +46,9 @@
      * Ut interface for the supplementary service configuration.
      */
     IImsUt getUtInterface(int serviceId);
+
+    /**
+     * Config interface to get/set IMS service/capability parameters.
+     */
+    IImsConfig getConfigInterface();
 }
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index 745c9d0..07d2aae 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -24,6 +24,7 @@
 import android.location.CountryDetector;
 import android.net.Uri;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.PhoneLookup;
 import android.provider.ContactsContract.RawContacts;
@@ -96,10 +97,18 @@
     public String numberLabel;
 
     public int photoResource;
-    public long person_id;
+
+    // Contact ID, which will be 0 if a contact comes from the corp CP2.
+    public long contactIdOrZero;
     public boolean needUpdate;
     public Uri contactRefUri;
 
+    /**
+     * Contact display photo URI.  If a contact has no display photo but a thumbnail, it'll be
+     * the thumbnail URI instead.
+     */
+    public Uri contactDisplayPhotoUri;
+
     // fields to hold individual contact preference data,
     // including the send to voicemail flag and the ringtone
     // uri reference.
@@ -209,16 +218,29 @@
                 // Look for the person_id.
                 columnIndex = getColumnIndexForPersonId(contactRef, cursor);
                 if (columnIndex != -1) {
-                    info.person_id = cursor.getLong(columnIndex);
-                    if (VDBG) Rlog.v(TAG, "==> got info.person_id: " + info.person_id);
+                    final long contactId = cursor.getLong(columnIndex);
+                    if (contactId != 0 && !Contacts.isCorpContactId(contactId)) {
+                        info.contactIdOrZero = contactId;
+                        if (VDBG) {
+                            Rlog.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
+                        }
+                    }
                 } else {
                     // No valid columnIndex, so we can't look up person_id.
-                    Rlog.w(TAG, "Couldn't find person_id column for " + contactRef);
+                    Rlog.w(TAG, "Couldn't find contact_id column for " + contactRef);
                     // Watch out: this means that anything that depends on
                     // person_id will be broken (like contact photo lookups in
                     // the in-call UI, for example.)
                 }
 
+                // Display photo URI.
+                columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
+                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+                    info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
+                } else {
+                    info.contactDisplayPhotoUri = null;
+                }
+
                 // look for the custom ringtone, create from the string stored
                 // in the database.
                 columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
@@ -303,7 +325,8 @@
             return new CallerInfo().markAsVoiceMail();
         }
 
-        Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+        Uri contactUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
+                Uri.encode(number));
 
         CallerInfo info = getCallerInfo(context, contactUri);
         info = doSecondaryLookupIfNecessary(context, number, info);
@@ -334,46 +357,13 @@
             String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
             if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
                 previousResult = getCallerInfo(context,
-                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+                        Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
                                 Uri.encode(username)));
             }
         }
         return previousResult;
     }
 
-    /**
-     * getCallerId: a convenience method to get the caller id for a given
-     * number.
-     *
-     * @param context the context used to get the ContentResolver.
-     * @param number a phone number.
-     * @return if the number belongs to a contact, the contact's name is
-     * returned; otherwise, the number itself is returned.
-     *
-     * TODO NOTE: This MAY need to refer to the Asynchronous Query API
-     * [startQuery()], instead of getCallerInfo, but since it looks like
-     * it is only being used by the provider calls in the messaging app:
-     *   1. android.provider.Telephony.Mms.getDisplayAddress()
-     *   2. android.provider.Telephony.Sms.getDisplayAddress()
-     * We may not need to make the change.
-     */
-    public static String getCallerId(Context context, String number) {
-        CallerInfo info = getCallerInfo(context, number);
-        String callerID = null;
-
-        if (info != null) {
-            String name = info.name;
-
-            if (!TextUtils.isEmpty(name)) {
-                callerID = name;
-            } else {
-                callerID = number;
-            }
-        }
-
-        return callerID;
-    }
-
     // Accessors
 
     /**
@@ -636,10 +626,10 @@
                     .append("\nnumberType: " + numberType)
                     .append("\nnumberLabel: " + numberLabel)
                     .append("\nphotoResource: " + photoResource)
-                    .append("\nperson_id: " + person_id)
+                    .append("\ncontactIdOrZero: " + contactIdOrZero)
                     .append("\nneedUpdate: " + needUpdate)
-                    .append("\ncontactRefUri: " + contactRefUri)
-                    .append("\ncontactRingtoneUri: " + contactRefUri)
+                    .append("\ncontactRingtoneUri: " + contactRingtoneUri)
+                    .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
                     .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
                     .append("\ncachedPhoto: " + cachedPhoto)
                     .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index fe403d9..120356b 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -376,51 +376,14 @@
 
         // Construct the URI object and query params, and start the query.
 
-        Uri contactRef;
-        String selection;
-        String[] selectionArgs;
-
-        if (PhoneNumberUtils.isUriNumber(number)) {
-            // "number" is really a SIP address.
-            if (DBG) Rlog.d(LOG_TAG, "  - Treating number as a SIP address: " + /*number*/ "xxxxxxx");
-
-            // We look up SIP addresses directly in the Data table:
-            contactRef = Data.CONTENT_URI;
-
-            // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
-            //
-            // Also note we use "upper(data1)" in the WHERE clause, and
-            // uppercase the incoming SIP address, in order to do a
-            // case-insensitive match.
-            //
-            // TODO: need to confirm that the use of upper() doesn't
-            // prevent us from using the index!  (Linear scan of the whole
-            // contacts DB can be very slow.)
-            //
-            // TODO: May also need to normalize by adding "sip:" as a
-            // prefix, if we start storing SIP addresses that way in the
-            // database.
-
-            selection = "upper(" + Data.DATA1 + ")=?"
-                    + " AND "
-                    + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
-            selectionArgs = new String[] { number.toUpperCase() };
-
-        } else {
-            // "number" is a regular phone number.  Use the PhoneLookup table:
-            contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
-            selection = null;
-            selectionArgs = null;
-        }
+        final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
+                .appendPath(number)
+                .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
+                        String.valueOf(PhoneNumberUtils.isUriNumber(number)))
+                .build();
 
         if (DBG) {
             Rlog.d(LOG_TAG, "==> contactRef: " + sanitizeUriToString(contactRef));
-            Rlog.d(LOG_TAG, "==> selection: " + selection);
-            if (selectionArgs != null) {
-                for (int i = 0; i < selectionArgs.length; i++) {
-                    Rlog.d(LOG_TAG, "==> selectionArgs[" + i + "]: " + selectionArgs[i]);
-                }
-            }
         }
 
         CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
@@ -446,8 +409,8 @@
                               cw,  // cookie
                               contactRef,  // uri
                               null,  // projection
-                              selection,  // selection
-                              selectionArgs,  // selectionArgs
+                              null,  // selection
+                              null,  // selectionArgs
                               null);  // orderBy
         return c;
     }
diff --git a/telephony/java/com/android/internal/telephony/IMms.aidl b/telephony/java/com/android/internal/telephony/IMms.aidl
index 4da90a5..db93a4f 100644
--- a/telephony/java/com/android/internal/telephony/IMms.aidl
+++ b/telephony/java/com/android/internal/telephony/IMms.aidl
@@ -17,6 +17,8 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.content.ContentValues;
+import android.net.Uri;
 
 /**
  * Service interface to handle MMS API requests
@@ -25,25 +27,28 @@
     /**
      * Send an MMS message
      *
+     * @param subId the SIM id
      * @param callingPkg the package name of the calling app
      * @param pdu the MMS message encoded in standard MMS PDU format
      * @param locationUrl the optional location url for where this message should be sent to
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is successfully sent, or failed
      */
-    void sendMessage(String callingPkg, in byte[] pdu, String locationUrl,
+    void sendMessage(long subId, String callingPkg, in byte[] pdu, String locationUrl,
             in PendingIntent sentIntent);
 
     /**
      * Download an MMS message using known location and transaction id
      *
+     * @param subId the SIM id
      * @param callingPkg the package name of the calling app
      * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
      *  from the MMS WAP push notification
      * @param downloadedIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is downloaded, or the download is failed
      */
-    void downloadMessage(String callingPkg, String locationUrl, in PendingIntent downloadedIntent);
+    void downloadMessage(long subId, String callingPkg, String locationUrl,
+            in PendingIntent downloadedIntent);
 
     /**
      * Update the status of a pending (send-by-IP) MMS message handled by the carrier app.
@@ -65,4 +70,149 @@
      *  will be downloaded via carrier network
      */
     void updateMmsDownloadStatus(int messageRef, in byte[] pdu);
+
+    /**
+     * Get carrier-dependent configuration value as boolean. For example, if multipart SMS
+     * is supported.
+     *
+     * @param name the configuration name
+     * @param defaultValue the default value if fail to find the name
+     */
+    boolean getCarrierConfigBoolean(String name, boolean defaultValue);
+
+    /**
+     * Get carrier-dependent configuration value as int. For example, the MMS message size limit.
+     *
+     * @param name the configuration name
+     * @param defaultValue the default value if fail to find the name
+     */
+    int getCarrierConfigInt(String name, int defaultValue);
+
+    /**
+     * Get carrier-dependent configuration value as String. For example, extra HTTP headers for
+     * MMS request.
+     *
+     * @param name the configuration name
+     * @param defaultValue the default value if fail to find the name
+     */
+    String getCarrierConfigString(String name, String defaultValue);
+
+    /**
+     * Set carrier-dependent configuration value as boolean. For example, if multipart SMS
+     * is supported.
+     *
+     * @param name the configuration name
+     * @param value the configuration value
+     */
+    void setCarrierConfigBoolean(String callingPkg, String name, boolean value);
+
+    /**
+     * Set carrier-dependent configuration value as int. For example, the MMS message size limit.
+     *
+     * @param name the configuration name
+     * @param value the configuration value
+     */
+    void setCarrierConfigInt(String callingPkg, String name, int value);
+
+    /**
+     * Set carrier-dependent configuration value as String. For example, extra HTTP headers for
+     * MMS request.
+     *
+     * @param name the configuration name
+     * @param value the configuration value
+     */
+    void setCarrierConfigString(String callingPkg, String name, String value);
+
+    /**
+     * Import a text message into system's SMS store
+     *
+     * @param callingPkg the calling app's package name
+     * @param address the destination address of the message
+     * @param type the type of the message
+     * @param text the message text
+     * @param timestampMillis the message timestamp in milliseconds
+     * @param seen if the message is seen
+     * @param read if the message is read
+     * @return the message URI, null if failed
+     */
+    Uri importTextMessage(String callingPkg, String address, int type, String text,
+            long timestampMillis, boolean seen, boolean read);
+
+    /**
+      * Import a multimedia message into system's MMS store
+      *
+      * @param callingPkg the package name of the calling app
+      * @param pdu the PDU of the message to import
+      * @param messageId the optional message id
+      * @param timestampSecs the message timestamp in seconds
+      * @param seen if the message is seen
+      * @param read if the message is read
+      * @return the message URI, null if failed
+      */
+    Uri importMultimediaMessage(String callingPkg, in byte[] pdu, String messageId,
+            long timestampSecs, boolean seen, boolean read);
+
+    /**
+     * Delete a system stored SMS or MMS message
+     *
+     * @param callingPkg the package name of the calling app
+     * @param messageUri the URI of the stored message
+     * @return true if deletion is successful, false otherwise
+     */
+    boolean deleteStoredMessage(String callingPkg, in Uri messageUri);
+
+    /**
+     * Delete a system stored SMS or MMS thread
+     *
+     * @param callingPkg the package name of the calling app
+     * @param conversationId the ID of the message conversation
+     * @return true if deletion is successful, false otherwise
+     */
+    boolean deleteStoredConversation(String callingPkg, long conversationId);
+
+    /**
+     * Update the status properties of a system stored SMS or MMS message, e.g.
+     * the read status of a message, etc.
+     *
+     * @param callingPkg the package name of the calling app
+     * @param messageUri the URI of the stored message
+     * @param statusValues a list of status properties in key-value pairs to update
+     * @return true if deletion is successful, false otherwise
+     */
+    boolean updateStoredMessageStatus(String callingPkg, in Uri messageUri,
+            in ContentValues statusValues);
+
+    /**
+     * Add a text message draft to system SMS store
+     *
+     * @param callingPkg the package name of the calling app
+     * @param address the destination address of message
+     * @param text the body of the message to send
+     * @return the URI of the stored draft message
+     */
+    Uri addTextMessageDraft(String callingPkg, String address, String text);
+
+    /**
+     * Add a multimedia message draft to system MMS store
+     *
+     * @param callingPkg the package name of the calling app
+     * @param pdu the PDU data of the draft MMS
+     * @return the URI of the stored draft message
+     */
+    Uri addMultimediaMessageDraft(String callingPkg, in byte[] pdu);
+
+    /**
+     * Send a system stored MMS message
+     *
+     * This is used for sending a previously sent, but failed-to-send, message or
+     * for sending a text message that has been stored as a draft.
+     *
+     * @param subId the SIM id
+     * @param callingPkg the package name of the calling app
+     * @param messageUri the URI of the stored message
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed
+     */
+    void sendStoredMessage(long subId, String callingPkg, in Uri messageUri,
+            in PendingIntent sentIntent);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 53429b6..abbdc4a 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.net.Uri;
 import com.android.internal.telephony.SmsRawData;
 
 /** Interface for applications to access the ICC phone book.
@@ -466,7 +467,6 @@
      */
     long getPreferredSmsSubscription();
 
-
     /**
      * Gets SMS format supported on IMS.  SMS over IMS format is
      * either 3GPP or 3GPP2.
@@ -491,12 +491,76 @@
      */
     String getImsSmsFormatUsingSubId(long subId);
 
-
-
-
     /*
      * Get SMS prompt property,  enabled or not
      * @return true if enabled, false otherwise
      */
     boolean isSMSPromptEnabled();
+
+    /**
+     * Send a system stored text message.
+     *
+     * This is used for sending a previously sent, but failed-to-send, message or
+     * for sending a text message that has been stored as a draft.
+     *
+     * @param subId the SIM id.
+     * @param callingPkg the package name of the calling app
+     * @param messageUri the URI of the stored message
+     * @param scAddress is the service center address or null to use the current default SMSC
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     */
+    void sendStoredText(long subId, String callingPkg, in Uri messageUri, String scAddress,
+            in PendingIntent sentIntent, in PendingIntent deliveryIntent);
+
+    /**
+     * Send a system stored multi-part text message.
+     *
+     * This is used for sending a previously sent, but failed-to-send, message or
+     * for sending a text message that has been stored as a draft.
+     * The provided <code>PendingIntent</code> lists should match the part number of the
+     * divided text of the stored message by using <code>divideMessage</code>
+     *
+     * @param subId the SIM id.
+     * @param callingPkg the package name of the calling app
+     * @param messageUri the URI of the stored message
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
+     *   or one of these errors:<br>
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *   <code>RESULT_ERROR_NULL_PDU</code><br>
+     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+     *   the extra "errorCode" containing a radio technology specific value,
+     *   generally only useful for troubleshooting.<br>
+     *   The per-application based SMS control checks sentIntent. If sentIntent
+     *   is NULL the caller will be checked against all unknown applications,
+     *   which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     */
+    void sendStoredMultipartText(long subId, String callingPkg, in Uri messageUri,
+                String scAddress, in List<PendingIntent> sentIntents,
+                in List<PendingIntent> deliveryIntents);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7bf3b3f..4592717 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -653,5 +653,58 @@
      * @return carrier privelege status defined in TelephonyManager.
      */
     int hasCarrierPrivileges();
+
+    /**
+     * Similar to above, but check for pkg whose name is pkgname.
+     */
+    int checkCarrierPrivilegesForPackage(String pkgname);
+
+    /**
+     * Set whether Android should display a simplified Mobile Network Settings UI.
+     * The setting won't be persisted during power cycle.
+     *
+     * @param subId for which the simplified UI should be enabled or disabled.
+     * @param enable true means enabling the simplified UI.
+     */
+    void enableSimplifiedNetworkSettings(long subId, boolean enable);
+
+    /**
+     * Get whether a simplified Mobile Network Settings UI is enabled.
+     *
+     * @param subId for which the simplified UI should be enabled or disabled.
+     * @return true if the simplified UI is enabled.
+     */
+    boolean getSimplifiedNetworkSettingsEnabled(long subId);
+
+    /**
+     * Set the phone number string and its alphatag for line 1 for display
+     * purpose only, for example, displayed in Phone Status. It won't change
+     * the actual MSISDN/MDN. This setting won't be persisted during power cycle
+     * and it should be set again after reboot.
+     *
+     * @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
+     */
+    void setLine1NumberForDisplay(long subId, String alphaTag, String number);
+
+    /**
+     * Returns the displayed dialing number string if it was set previously via
+     * {@link #setLine1NumberForDisplay}. Otherwise returns null.
+     *
+     * @param subId whose dialing number for line 1 is returned.
+     * @return the displayed dialing number if set, or null if not set.
+     */
+    String getLine1NumberForDisplay(long subId);
+
+    /**
+     * Returns the displayed alphatag of the dialing number if it was set
+     * previously via {@link #setLine1NumberForDisplay}. Otherwise returns null.
+     *
+     * @param subId whose alphatag associated with line 1 is returned.
+     * @return the displayed alphatag of the dialing number if set, or null if
+     *         not set.
+     */
+    String getLine1AlphaTagForDisplay(long subId);
 }
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
index 90db818..fbe7856 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
@@ -223,12 +223,11 @@
             float left = 40.0f;
             float bottom = 40.0f + mDrawHeight;
 
-            mPaint.setShader(mGradient);
-
             mMatrix.setScale(1, mDrawWidth);
             mMatrix.postRotate(90);
             mMatrix.postTranslate(right, top);
             mGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mGradient);
             canvas.drawRect(right - mDrawWidth, top, right, top + mDrawHeight, mPaint);
 
             top += 40.0f + mDrawHeight;
@@ -237,6 +236,7 @@
             mMatrix.setScale(1, mDrawHeight);
             mMatrix.postTranslate(left, top);
             mGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mGradient);
             canvas.drawRect(left, top, right, top + mDrawHeight, mPaint);
 
             left += 40.0f + mDrawWidth;
@@ -248,6 +248,7 @@
             mMatrix.postRotate(180);
             mMatrix.postTranslate(left, bottom);
             mGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mGradient);
             canvas.drawRect(left, bottom - mDrawHeight, right, bottom, mPaint);
 
             top += 40.0f + mDrawHeight;
@@ -257,6 +258,7 @@
             mMatrix.postRotate(-90);
             mMatrix.postTranslate(left, top);
             mGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mGradient);
             canvas.drawRect(left, top, left + mDrawWidth, bottom, mPaint);
 
             right = left + mDrawWidth;
@@ -264,12 +266,11 @@
             top = bottom + 20.0f;
             bottom = top + 50.0f;
 
-            mPaint.setShader(mGradientStops);
-
             mMatrix.setScale(1, mDrawWidth);
             mMatrix.postRotate(90);
             mMatrix.postTranslate(right, top);
             mGradientStops.setLocalMatrix(mMatrix);
+            mPaint.setShader(mGradientStops);
             canvas.drawRect(left, top, right, bottom, mPaint);
             
             canvas.restore();
diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml
index 4c0c67a..d55736f 100644
--- a/tests/VoiceInteraction/res/layout/test_interaction.xml
+++ b/tests/VoiceInteraction/res/layout/test_interaction.xml
@@ -35,6 +35,13 @@
         android:textColor="#ffffffff"
         />
 
+    <Button android:id="@+id/complete"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:text="@string/completeVoice"
+        />
+
     <Button android:id="@+id/abort"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
index 142d781..002f350 100644
--- a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
+++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
@@ -50,6 +50,11 @@
                     android:layout_height="wrap_content"
                     android:text="@string/confirm"
                     />
+                <Button android:id="@+id/complete"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/complete"
+                    />
                 <Button android:id="@+id/abort"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
index 70baa52..7eec90c 100644
--- a/tests/VoiceInteraction/res/values/strings.xml
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -19,7 +19,9 @@
     <string name="start">Start</string>
     <string name="confirm">Confirm</string>
     <string name="abort">Abort</string>
+    <string name="complete">Complete</string>
     <string name="abortVoice">Abort Voice</string>
+    <string name="completeVoice">Complete Voice</string>
 
 </resources>
 
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index c24a088..d20906e 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -34,6 +34,7 @@
     TextView mText;
     Button mStartButton;
     Button mConfirmButton;
+    Button mCompleteButton;
     Button mAbortButton;
 
     static final int STATE_IDLE = 0;
@@ -41,6 +42,7 @@
     static final int STATE_CONFIRM = 2;
     static final int STATE_COMMAND = 3;
     static final int STATE_ABORT_VOICE = 4;
+    static final int STATE_COMPLETE_VOICE = 5;
 
     int mState = STATE_IDLE;
     Request mPendingRequest;
@@ -64,6 +66,8 @@
         mStartButton.setOnClickListener(this);
         mConfirmButton = (Button)mContentView.findViewById(R.id.confirm);
         mConfirmButton.setOnClickListener(this);
+        mCompleteButton = (Button)mContentView.findViewById(R.id.complete);
+        mCompleteButton.setOnClickListener(this);
         mAbortButton = (Button)mContentView.findViewById(R.id.abort);
         mAbortButton.setOnClickListener(this);
         updateState();
@@ -74,6 +78,7 @@
         mStartButton.setEnabled(mState == STATE_IDLE);
         mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND);
         mAbortButton.setEnabled(mState == STATE_ABORT_VOICE);
+        mCompleteButton.setEnabled(mState == STATE_COMPLETE_VOICE);
     }
 
     public void onClick(View v) {
@@ -95,6 +100,11 @@
             mPendingRequest = null;
             mState = STATE_IDLE;
             updateState();
+        } else if (v== mCompleteButton) {
+            mPendingRequest.sendCompleteVoiceResult(null);
+            mPendingRequest = null;
+            mState = STATE_IDLE;
+            updateState();
         }
     }
 
@@ -114,6 +124,15 @@
     }
 
     @Override
+    public void onCompleteVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+        Log.i(TAG, "onCompleteVoice: message=" + message + " extras=" + extras);
+        mText.setText(message);
+        mPendingRequest = request;
+        mState = STATE_COMPLETE_VOICE;
+        updateState();
+    }
+
+    @Override
     public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
         Log.i(TAG, "onAbortVoice: message=" + message + " extras=" + extras);
         mText.setText(message);
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index 3ae6a36..d64eefa 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -30,6 +30,7 @@
 
     VoiceInteractor mInteractor;
     Button mAbortButton;
+    Button mCompleteButton;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -44,6 +45,8 @@
         setContentView(R.layout.test_interaction);
         mAbortButton = (Button)findViewById(R.id.abort);
         mAbortButton.setOnClickListener(this);
+        mCompleteButton = (Button)findViewById(R.id.complete);
+        mCompleteButton.setOnClickListener(this);
 
         // Framework should take care of these.
         getWindow().setGravity(Gravity.TOP);
@@ -90,6 +93,21 @@
                 }
             };
             mInteractor.submitRequest(req);
+        } else if (v == mCompleteButton) {
+            VoiceInteractor.CompleteVoiceRequest req = new VoiceInteractor.CompleteVoiceRequest(
+                    "Woohoo, completed!", null) {
+                @Override
+                public void onCancel() {
+                    Log.i(TAG, "Canceled!");
+                }
+
+                @Override
+                public void onCompleteResult(Bundle result) {
+                    Log.i(TAG, "Complete result: result=" + result);
+                    getActivity().finish();
+                }
+            };
+            mInteractor.submitRequest(req);
         }
     }
 
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 105803e..f939678 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -828,9 +828,18 @@
 
     @Override
     public int[] extractThemeAttrs() {
+        // The drawables are always inflated with a Theme and we don't care about caching. So,
+        // just return.
         return null;
     }
 
+    @Override
+    public int getChangingConfigurations() {
+        // We don't care about caching. Any change in configuration is a fresh render. So,
+        // just return.
+        return 0;
+    }
+
     /**
      * Retrieve the raw TypedValue for the attribute at <var>index</var>.
      *
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index bd88ae2..f4282ad 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -53,6 +53,7 @@
  */
 public final class Bitmap_Delegate {
 
+
     public enum BitmapCreateFlags {
         PREMULTIPLIED, MUTABLE
     }
@@ -68,6 +69,7 @@
     private BufferedImage mImage;
     private boolean mHasAlpha = true;
     private boolean mHasMipMap = false;      // TODO: check the default.
+    private boolean mIsPremultiplied = true;
     private int mGenerationId = 0;
 
 
@@ -393,21 +395,19 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y,
-            boolean isPremultiplied) {
+    /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) {
         // get the delegate from the native int.
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return 0;
         }
 
-        // TODO: Support isPremultiplied.
         return delegate.mImage.getRGB(x, y);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset,
-            int stride, int x, int y, int width, int height, boolean isPremultiplied) {
+            int stride, int x, int y, int width, int height) {
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return;
@@ -418,8 +418,7 @@
 
 
     @LayoutlibDelegate
-    /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color,
-            boolean isPremultiplied) {
+    /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return;
@@ -430,7 +429,7 @@
 
     @LayoutlibDelegate
     /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset,
-            int stride, int x, int y, int width, int height, boolean isPremultiplied) {
+            int stride, int x, int y, int width, int height) {
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
         if (delegate == null) {
             return;
@@ -518,7 +517,26 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nativeSetAlphaAndPremultiplied(long nativeBitmap, boolean hasAlpha,
+    /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        return delegate != null && delegate.mIsPremultiplied;
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mIsPremultiplied = isPremul;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha,
             boolean isPremul) {
         // get the delegate from the native int.
         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 887e8f6..f044def6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -17,19 +17,14 @@
 package android.graphics;
 
 import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
-import android.content.res.AssetManager;
 import android.graphics.FontFamily_Delegate.FontVariant;
 
 import java.awt.Font;
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -58,10 +53,8 @@
 
     // ---- delegate data ----
 
-    @Nullable
+    @NonNull
     private final FontFamily_Delegate[] mFontFamilies;  // the reference to FontFamily_Delegate.
-    @Nullable
-    private final Font mFont;
     /** @see FontFamily_Delegate.FontInfo#mStyle */
     private final int mStyle;
 
@@ -87,11 +80,6 @@
     @NonNull
     public List<Font> getFonts(FontVariant variant) {
         assert variant != FontVariant.NONE;
-        if (mFontFamilies == null) {
-            // We don't care about the variant here, since the Typeface was created with a single
-            // Font File. So, we simply return that Font.
-            return getFontAsList();
-        }
         List<Font> fonts = new ArrayList<Font>(mFontFamilies.length);
         for (int i = 0; i < mFontFamilies.length; i++) {
             FontFamily_Delegate ffd = mFontFamilies[i];
@@ -128,14 +116,6 @@
     // ---- native methods ----
 
     @LayoutlibDelegate
-    /*package*/ static synchronized long nativeCreate(String familyName, int style) {
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Could not find font with family \"" + familyName + "\".",
-                null /*throwable*/, null /*data*/);
-        return 0;
-    }
-
-    @LayoutlibDelegate
     /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) {
         Typeface_Delegate delegate = sManager.getDelegate(native_instance);
         if (delegate == null) {
@@ -149,24 +129,6 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static synchronized long nativeCreateFromAsset(AssetManager mgr, String path) {
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/);
-        return 0;
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static synchronized long nativeCreateFromFile(String path) {
-        if (!path.startsWith(SYSTEM_FONTS)) {
-            Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                    "Typeface.createFromFile() can only work with platform fonts located in " +
-                            SYSTEM_FONTS, null, null);
-            return 0;
-        }
-        return sManager.addNewDelegate(new Typeface_Delegate(path));
-    }
-
-    @LayoutlibDelegate
     /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) {
         FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length];
         for (int i = 0; i < familyArray.length; i++) {
@@ -203,22 +165,8 @@
 
     // ---- Private delegate/helper methods ----
 
-    private Typeface_Delegate(FontFamily_Delegate[] fontFamilies, int style) {
+    private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) {
         mFontFamilies = fontFamilies;
-        mFont = null;
         mStyle = style;
     }
-
-    private Typeface_Delegate(String path) {
-        mFont = FontFamily_Delegate.loadFont(path);
-        mFontFamilies = null;
-        mStyle = FontFamily_Delegate.getFontStyle(path);
-    }
-
-    private List<Font> getFontAsList() {
-        if (mFont != null) {
-            return Collections.singletonList(mFont);
-        }
-        return Collections.emptyList();
-    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 9ec6f4d..c44a57c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -237,9 +237,4 @@
         // TODO Auto-generated method stub
         return null;
     }
-
-    @Override
-    public void setCursorAnchorMonitorMode(IBinder arg0, int arg1) {
-      // TODO Auto-generated method stub
-    }
 }
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index 32625ae..8de64db 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -131,8 +131,8 @@
 Mac has horrible font rendering support.
 
 ReplaceMethodCallsAdapter replaces calls to certain methods. Currently, it only rewrites calls to
-java.lang.System.arraycopy([CI[CII)V, which is not part of the Desktop VM to call the more general
-method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V.
+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.
 
 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
@@ -236,7 +236,7 @@
 implementation.
 b- A brand new implementation of SomeClass.MethodName() which calls to a non-existing static method
 named SomeClass_Delegate.MethodName(). The implementation of this 'delegate' method is done in
-layoutlib_brigde.
+layoutlib_bridge.
 
 The delegate method is a static method. If the original method is non-static, the delegate method
 receives the original 'this' as its first argument. If the original method is an inner non-static
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 8373e30..767e597 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,9 @@
                 considerDesc(desc);
 
 
-                // Check if method is java.lang.System.arrayCopy([CI[CII)V
+                // Check if method is a specialized version of java.lang.System.arrayCopy()
                 if (owner.equals("java/lang/System") && name.equals("arraycopy")
-                        && desc.equals("([CI[CII)V")) {
+                        && !desc.equals("(Ljava/lang/Object;ILjava/lang/Object;II)V")) {
                     mReplaceMethodCallClasses.add(mOwnerClass);
                 }
             }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index c96a143..bd6f070 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -216,21 +216,21 @@
             String name = classToEntryPath(clazz);
             InputStream is = ClassLoader.getSystemResourceAsStream(name);
             ClassReader cr = new ClassReader(is);
-            byte[] b = transform(cr, true /* stubNativesOnly */);
+            byte[] b = transform(cr, true);
             name = classNameToEntryPath(transformName(cr.getClassName()));
             all.put(name, b);
         }
 
         for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
             ClassReader cr = entry.getValue();
-            byte[] b = transform(cr, true /* stubNativesOnly */);
+            byte[] b = transform(cr, true);
             String name = classNameToEntryPath(transformName(cr.getClassName()));
             all.put(name, b);
         }
 
         for (Entry<String, ClassReader> entry : mKeep.entrySet()) {
             ClassReader cr = entry.getValue();
-            byte[] b = transform(cr, true /* stubNativesOnly */);
+            byte[] b = transform(cr, true);
             String name = classNameToEntryPath(transformName(cr.getClassName()));
             all.put(name, b);
         }
@@ -282,7 +282,7 @@
 
     /**
      * Utility method to get the JAR entry path from a Class name.
-     * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
+     * e.g. it returns something like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
      */
     private String classToEntryPath(Class<?> clazz) {
         String name = "";
@@ -345,10 +345,8 @@
             cv = new RenameClassAdapter(cv, className, newName);
         }
 
-        cv = new TransformClassAdapter(mLog, mStubMethods,
-                mDeleteReturns.get(className),
-                newName, cv,
-                stubNativesOnly, stubNativesOnly || hasNativeMethods);
+        cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
+                newName, cv, stubNativesOnly);
 
         Set<String> delegateMethods = mDelegateMethods.get(className);
         if (delegateMethods != null && !delegateMethods.isEmpty()) {
@@ -361,7 +359,7 @@
             }
         }
 
-        cr.accept(cv, 0 /* flags */);
+        cr.accept(cv, 0);
         return cw.toByteArray();
     }
 
@@ -395,7 +393,7 @@
      */
     boolean hasNativeMethods(ClassReader cr) {
         ClassHasNativeVisitor cv = new ClassHasNativeVisitor();
-        cr.accept(cv, 0 /* flags */);
+        cr.accept(cv, 0);
         return cv.hasNativeMethods();
     }
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index 927be97..3d89c68 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -32,10 +32,10 @@
 
     /** Suffix added to original methods. */
     private static final String ORIGINAL_SUFFIX = "_Original";
-    private static String CONSTRUCTOR = "<init>";
-    private static String CLASS_INIT = "<clinit>";
+    private static final String CONSTRUCTOR = "<init>";
+    private static final String CLASS_INIT = "<clinit>";
 
-    public final static String ALL_NATIVES = "<<all_natives>>";
+    public static final String ALL_NATIVES = "<<all_natives>>";
 
     private final String mClassName;
     private final Set<String> mDelegateMethods;
@@ -78,19 +78,16 @@
                               mDelegateMethods.contains(name);
 
         if (!useDelegate) {
-            // Not creating a delegate for this method, pass it as-is from the reader
-            // to the writer.
+            // Not creating a delegate for this method, pass it as-is from the reader to the writer.
             return super.visitMethod(access, name, desc, signature, exceptions);
         }
 
-        if (useDelegate) {
-            if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
-                // We don't currently support generating delegates for constructors.
-                throw new UnsupportedOperationException(
-                    String.format(
-                        "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",  //$NON-NLS-1$
-                        mClassName, name, desc));
-            }
+        if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
+            // We don't currently support generating delegates for constructors.
+            throw new UnsupportedOperationException(
+                String.format(
+                    "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",  //$NON-NLS-1$
+                    mClassName, name, desc));
         }
 
         if (isNative) {
@@ -98,8 +95,8 @@
             access = access & ~Opcodes.ACC_NATIVE;
             MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
 
-            DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
-                    mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic);
+            DelegateMethodAdapter a = new DelegateMethodAdapter(
+                    mLog, null, mwDelegate, mClassName, name, desc, isStatic);
 
             // A native has no code to visit, so we need to generate it directly.
             a.generateDelegateCode();
@@ -112,22 +109,16 @@
         //   The content is the original method as-is from the reader.
         // - A brand new implementation of SomeClass.MethodName() which calls to a
         //   non-existing method named SomeClass_Delegate.MethodName().
-        //   The implementation of this 'delegate' method is done in layoutlib_brigde.
+        //   The implementation of this 'delegate' method is done in layoutlib_bridge.
 
         int accessDelegate = access;
-        // change access to public for the original one
-        if (Main.sOptions.generatePublicAccess) {
-            access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
-            access |= Opcodes.ACC_PUBLIC;
-        }
 
         MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
                                                      desc, signature, exceptions);
         MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name,
                                                      desc, signature, exceptions);
 
-        DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+        return new DelegateMethodAdapter(
                 mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
-        return a;
     }
 }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
similarity index 97%
rename from tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
rename to tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index 0000b22..12690db 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -71,7 +71,7 @@
  * Instances of this class are not re-usable.
  * The class adapter creates a new instance for each method.
  */
-class DelegateMethodAdapter2 extends MethodVisitor {
+class DelegateMethodAdapter extends MethodVisitor {
 
     /** Suffix added to delegate classes. */
     public static final String DELEGATE_SUFFIX = "_Delegate";
@@ -97,10 +97,10 @@
     private Object[] mDelegateLineNumber;
 
     /**
-     * Creates a new {@link DelegateMethodAdapter2} that will transform this method
+     * Creates a new {@link DelegateMethodAdapter} that will transform this method
      * into a delegate call.
      * <p/>
-     * See {@link DelegateMethodAdapter2} for more details.
+     * See {@link DelegateMethodAdapter} for more details.
      *
      * @param log The logger object. Must not be null.
      * @param mvOriginal The parent method writer to copy of the original method.
@@ -114,7 +114,7 @@
      *          {@link Type#getArgumentTypes(String)})
      * @param isStatic True if the method is declared static.
      */
-    public DelegateMethodAdapter2(Log log,
+    public DelegateMethodAdapter(Log log,
             MethodVisitor mvOriginal,
             MethodVisitor mvDelegate,
             String className,
@@ -138,7 +138,7 @@
      * (since they have no code to visit).
      * <p/>
      * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
-     * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern
+     * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
      * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
      * this method will be invoked from {@link MethodVisitor#visitEnd()}.
      */
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index ad10656..ea9ce10 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -49,7 +49,6 @@
 public class Main {
 
     public static class Options {
-        public boolean generatePublicAccess = true;
         public boolean listAllDeps = false;
         public boolean listOnlyMissingDeps = false;
     }
@@ -196,8 +195,6 @@
         for (String s : args) {
             if (s.equals("-v")) {
                 log.setVerbose(true);
-            } else if (s.equals("-p")) {
-                sOptions.generatePublicAccess = false;
             } else if (s.equals("--list-deps")) {
                 sOptions.listAllDeps = true;
                 needs_dest = false;
@@ -225,8 +222,6 @@
             return false;
         }
 
-        sOptions.generatePublicAccess = false;
-
         return true;
     }
 }
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 e57eba1..ae17417 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,10 +20,23 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Replaces calls to certain methods that do not exist in the Desktop VM.
  */
 public class ReplaceMethodCallsAdapter extends ClassVisitor {
+
+    /**
+     * Descriptors for specialized versions {@link System#arraycopy} that are not present on the
+     * Desktop VM.
+     */
+    private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<String>(Arrays.asList(
+            "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
+            "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
+
     public ReplaceMethodCallsAdapter(ClassVisitor cv) {
         super(Opcodes.ASM4, cv);
     }
@@ -42,10 +55,12 @@
 
         @Override
         public void visitMethodInsn(int opcode, String owner, String name, String desc) {
-            // Check if method is java.lang.System.arrayCopy([CI[CII)V
-            if (owner.equals("java/lang/System") && name.equals("arraycopy")
-                    && desc.equals("([CI[CII)V")) {
-                desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+            // 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";
+                }
             }
             super.visitMethodInsn(opcode, owner, name, desc);
         }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index d45a183..0b869a5 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -40,19 +40,16 @@
 
     /**
      * Creates a new class adapter that will stub some or all methods.
-     * @param logger
      * @param stubMethods  list of method signatures to always stub out
      * @param deleteReturns list of types that trigger the deletion of methods returning them.
      * @param className The name of the class being modified
      * @param cv The parent class writer visitor
      * @param stubNativesOnly True if only native methods should be stubbed. False if all
      *                        methods should be stubbed.
-     * @param hasNative True if the method has natives, in which case its access should be
-     *                  changed.
      */
     public TransformClassAdapter(Log logger, Set<String> stubMethods,
             Set<String> deleteReturns, String className, ClassVisitor cv,
-            boolean stubNativesOnly, boolean hasNative) {
+            boolean stubNativesOnly) {
         super(Opcodes.ASM4, cv);
         mLog = logger;
         mStubMethods = stubMethods;
@@ -70,11 +67,6 @@
         // This class might be being renamed.
         name = mClassName;
 
-        // remove protected or private and set as public
-        if (Main.sOptions.generatePublicAccess) {
-            access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
-            access |= Opcodes.ACC_PUBLIC;
-        }
         // remove final
         access = access & ~Opcodes.ACC_FINAL;
         // note: leave abstract classes as such
@@ -87,11 +79,6 @@
     /* Visits the header of an inner class. */
     @Override
     public void visitInnerClass(String name, String outerName, String innerName, int access) {
-        // remove protected or private and set as public
-        if (Main.sOptions.generatePublicAccess) {
-            access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
-            access |= Opcodes.ACC_PUBLIC;
-        }
         // remove final
         access = access & ~Opcodes.ACC_FINAL;
         // note: leave abstract classes as such
@@ -119,12 +106,6 @@
 
         String methodSignature = mClassName.replace('/', '.') + "#" + name;
 
-        // change access to public
-        if (Main.sOptions.generatePublicAccess) {
-            access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
-            access |= Opcodes.ACC_PUBLIC;
-        }
-
         // remove final
         access = access & ~Opcodes.ACC_FINAL;
 
@@ -159,11 +140,6 @@
     @Override
     public FieldVisitor visitField(int access, String name, String desc, String signature,
             Object value) {
-        // change access to public
-        if (Main.sOptions.generatePublicAccess) {
-            access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
-            access |= Opcodes.ACC_PUBLIC;
-        }
         return super.visitField(access, name, desc, signature, value);
     }
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 6e120ce..94aad1d 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -130,7 +130,7 @@
     }
 
     /**
-     * {@link DelegateMethodAdapter2} does not support overriding constructors yet,
+     * {@link DelegateMethodAdapter} does not support overriding constructors yet,
      * so this should fail with an {@link UnsupportedOperationException}.
      *
      * Although not tested here, the message of the exception should contain the
@@ -258,9 +258,8 @@
                     // Check the inner class. Since it's not a static inner class, we need
                     // to use the hidden constructor that takes the outer class as first parameter.
                     Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
-                    Constructor<?> innerCons = innerClazz2.getConstructor(
-                                                                new Class<?>[] { outerClazz2 });
-                    Object i2 = innerCons.newInstance(new Object[] { o2 });
+                    Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2);
+                    Object i2 = innerCons.newInstance(o2);
                     assertNotNull(i2);
 
                     // The original Inner.get returns 3+10+20,
@@ -344,10 +343,10 @@
          */
         public int callGet(Object instance, int a, long b) throws Exception {
             Method m = instance.getClass().getMethod("get",
-                    new Class<?>[] { int.class, long.class } );
+                    int.class, long.class);
 
-            Object result = m.invoke(instance, new Object[] { a, b });
-            return ((Integer) result).intValue();
+            Object result = m.invoke(instance, a, b);
+            return (Integer) result;
         }
 
         /**
@@ -356,10 +355,10 @@
          */
         public int callGet_Original(Object instance, int a, long b) throws Exception {
             Method m = instance.getClass().getMethod("get_Original",
-                    new Class<?>[] { int.class, long.class } );
+                    int.class, long.class);
 
-            Object result = m.invoke(instance, new Object[] { a, b });
-            return ((Integer) result).intValue();
+            Object result = m.invoke(instance, a, b);
+            return (Integer) result;
         }
 
         /**
@@ -388,10 +387,10 @@
          */
         public int callAdd(Object instance, int a, int b) throws Exception {
             Method m = instance.getClass().getMethod("add",
-                    new Class<?>[] { int.class, int.class });
+                    int.class, int.class);
 
-            Object result = m.invoke(instance, new Object[] { a, b });
-            return ((Integer) result).intValue();
+            Object result = m.invoke(instance, a, b);
+            return (Integer) result;
         }
 
         /**
@@ -401,10 +400,10 @@
         public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
                 throws Exception {
             Method m = instance.getClass().getMethod("callNativeInstance",
-                    new Class<?>[] { int.class, double.class, Object[].class });
+                    int.class, double.class, Object[].class);
 
-            Object result = m.invoke(instance, new Object[] { a, d, o });
-            return ((Integer) result).intValue();
+            Object result = m.invoke(instance, a, d, o);
+            return (Integer) result;
         }
 
         public abstract void testModifiedInstance() throws Exception;
@@ -442,8 +441,8 @@
                 StringWriter sw = new StringWriter();
                 PrintWriter pw = new PrintWriter(sw);
                 // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
-                Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() });
-                Object tcv = cons.newInstance(new Object[] { pw });
+                Constructor<?> cons = tcvClass.getConstructor(pw.getClass());
+                Object tcv = cons.newInstance(pw);
                 ClassReader cr2 = new ClassReader(bytes);
                 cr2.accept((ClassVisitor) tcv, 0 /* flags */);
 
@@ -452,8 +451,7 @@
             }
 
             // Re-throw exception with new message
-            RuntimeException ex = new RuntimeException(sb.toString(), t);
-            return ex;
+            return new RuntimeException(sb.toString(), t);
         } catch (Throwable ignore) {
             // In case of problem, just throw the original exception as-is.
             return t;
diff --git a/wifi/java/android/net/wifi/WifiAdapter.java b/wifi/java/android/net/wifi/WifiAdapter.java
index f6ee730..c0211f3 100644
--- a/wifi/java/android/net/wifi/WifiAdapter.java
+++ b/wifi/java/android/net/wifi/WifiAdapter.java
@@ -19,34 +19,57 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/** @hide */
+/**
+ * Represents local wifi adapter. Different devices have different kinds of
+ * wifi adapters; each with different capabilities. Use this class to find out
+ * which capabilites are supported by the wifi adapter on the device.
+ */
 public class WifiAdapter implements Parcelable {
 
     /* Keep this list in sync with wifi_hal.h */
+    /** @hide */
     public static final int WIFI_FEATURE_INFRA            = 0x0001;  // Basic infrastructure mode
+    /** @hide */
     public static final int WIFI_FEATURE_INFRA_5G         = 0x0002;  // Support for 5 GHz Band
+    /** @hide */
     public static final int WIFI_FEATURE_PASSPOINT        = 0x0004;  // Support for GAS/ANQP
+    /** @hide */
     public static final int WIFI_FEATURE_P2P              = 0x0008;  // Wifi-Direct
+    /** @hide */
     public static final int WIFI_FEATURE_MOBILE_HOTSPOT   = 0x0010;  // Soft AP
+    /** @hide */
     public static final int WIFI_FEATURE_SCANNER          = 0x0020;  // WifiScanner APIs
+    /** @hide */
     public static final int WIFI_FEATURE_NAN              = 0x0040;  // Neighbor Awareness Networking
+    /** @hide */
     public static final int WIFI_FEATURE_D2D_RTT          = 0x0080;  // Device-to-device RTT
+    /** @hide */
     public static final int WIFI_FEATURE_D2AP_RTT         = 0x0100;  // Device-to-AP RTT
+    /** @hide */
     public static final int WIFI_FEATURE_BATCH_SCAN       = 0x0200;  // Batched Scan (deprecated)
+    /** @hide */
     public static final int WIFI_FEATURE_PNO              = 0x0400;  // Preferred network offload
+    /** @hide */
     public static final int WIFI_FEATURE_ADDITIONAL_STA   = 0x0800;  // Support for two STAs
+    /** @hide */
     public static final int WIFI_FEATURE_TDLS             = 0x1000;  // Tunnel directed link setup
+    /** @hide */
     public static final int WIFI_FEATURE_TDLS_OFFCHANNEL  = 0x2000;  // Support for TDLS off channel
+    /** @hide */
     public static final int WIFI_FEATURE_EPR              = 0x4000;  // Enhanced power reporting
 
     private String name;
     private int    supportedFeatures;
 
+    /** @hide */
     public WifiAdapter(String name, int supportedFeatures) {
         this.name = name;
         this.supportedFeatures = supportedFeatures;
     }
 
+    /**
+     * @return name of the adapter
+     */
     public String getName() {
         return name;
     }
@@ -59,68 +82,121 @@
         return (supportedFeatures & feature) == feature;
     }
 
+    /**
+     * @return true if this adapter supports 5 GHz band
+     */
+    public boolean is5GHzBandSupported() {
+        return isFeatureSupported(WIFI_FEATURE_INFRA_5G);
+    }
+
+    /**
+     * @return true if this adapter supports passpoint
+     */
     public boolean isPasspointSupported() {
         return isFeatureSupported(WIFI_FEATURE_PASSPOINT);
     }
 
-    public boolean isWifiDirectSupported() {
+    /**
+     * @return true if this adapter supports WifiP2pManager (Wi-Fi Direct)
+     */
+    public boolean isP2pSupported() {
         return isFeatureSupported(WIFI_FEATURE_P2P);
     }
 
-    public boolean isMobileHotstpoSupported() {
+    /**
+     * @return true if this adapter supports portable Wi-Fi hotspot
+     */
+    public boolean isPortableHotspotSupported() {
         return isFeatureSupported(WIFI_FEATURE_MOBILE_HOTSPOT);
     }
 
+    /**
+     * @return true if this adapter supports WifiScanner APIs
+     */
     public boolean isWifiScannerSupported() {
         return isFeatureSupported(WIFI_FEATURE_SCANNER);
     }
 
+    /**
+     * @return true if this adapter supports Neighbour Awareness Network APIs
+     * @hide
+     */
     public boolean isNanSupported() {
         return isFeatureSupported(WIFI_FEATURE_NAN);
     }
 
+    /**
+     * @return true if this adapter supports Device-to-device RTT
+     */
     public boolean isDeviceToDeviceRttSupported() {
         return isFeatureSupported(WIFI_FEATURE_D2D_RTT);
     }
 
+    /**
+     * @return true if this adapter supports Device-to-AP RTT
+     */
     public boolean isDeviceToApRttSupported() {
         return isFeatureSupported(WIFI_FEATURE_D2AP_RTT);
     }
 
+    /**
+     * @return true if this adapter supports offloaded connectivity scan
+     */
     public boolean isPreferredNetworkOffloadSupported() {
         return isFeatureSupported(WIFI_FEATURE_PNO);
     }
 
+    /**
+     * @return true if this adapter supports multiple simultaneous connections
+     * @hide
+     */
     public boolean isAdditionalStaSupported() {
         return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
     }
 
+    /**
+     * @return true if this adapter supports Tunnel Directed Link Setup
+     */
     public boolean isTdlsSupported() {
         return isFeatureSupported(WIFI_FEATURE_TDLS);
     }
 
+    /**
+     * @return true if this adapter supports Off Channel Tunnel Directed Link Setup
+     */
     public boolean isOffChannelTdlsSupported() {
         return isFeatureSupported(WIFI_FEATURE_TDLS_OFFCHANNEL);
     }
 
+    /**
+     * @return true if this adapter supports advanced power/performance counters
+     */
     public boolean isEnhancedPowerReportingSupported() {
         return isFeatureSupported(WIFI_FEATURE_EPR);
     }
 
     /* Parcelable implementation */
-
-    /** Implement the Parcelable interface {@hide} */
+    /**
+     * Implement the Parcelable interface
+     * {@hide}
+     */
     public int describeContents() {
         return 0;
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /**
+     * Implement the Parcelable interface
+     * {@hide}
+     */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(name);
         dest.writeInt(supportedFeatures);
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /**
+     * Implement the Parcelable interface
+     * {@hide}
+     */
     public static final Creator<WifiAdapter> CREATOR =
             new Creator<WifiAdapter>() {
                 public WifiAdapter createFromParcel(Parcel in) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f9a9e7d..cf4cb89 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -568,9 +568,10 @@
     }
 
     /**
-     * @hide
+     * Retrieve all wifi adapters available on this device
+     * @return list of adapters
      */
-    public List<WifiAdapter> getAdaptors() {
+    public List<WifiAdapter> getAdapters() {
         try {
             return mService.getAdaptors();
         } catch (RemoteException e) {