Merge "Clarify some comments on individual key codes." into gingerbread
diff --git a/api/current.xml b/api/current.xml
index 84ccd45..b657c34 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1145,6 +1145,17 @@
  visibility="public"
 >
 </field>
+<field name="USE_SIP"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.permission.USE_SIP&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="VIBRATE"
  type="java.lang.String"
  transient="false"
@@ -48191,6 +48202,16 @@
  visibility="public"
 >
 </field>
+<field name="firstInstallTime"
+ type="long"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="gids"
  type="int[]"
  transient="false"
@@ -48213,6 +48234,16 @@
  visibility="public"
 >
 </field>
+<field name="lastUpdateTime"
+ type="long"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="packageName"
  type="java.lang.String"
  transient="false"
@@ -79272,7 +79303,7 @@
  type="float"
  transient="false"
  volatile="false"
- value="0.001f"
+ value="0.0010f"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -99439,6 +99470,1945 @@
 </field>
 </class>
 </package>
+<package name="android.net.sip"
+>
+<class name="SipAudioCall"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SipAudioCall"
+ type="android.net.sip.SipAudioCall"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="localProfile" type="android.net.sip.SipProfile">
+</parameter>
+</constructor>
+<method name="answerCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="timeout" type="int">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="attachCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="sessionDescription" type="java.lang.String">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="continueCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="timeout" type="int">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="endCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="getLocalProfile"
+ return="android.net.sip.SipProfile"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPeerProfile"
+ return="android.net.sip.SipProfile"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getState"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="holdCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="timeout" type="int">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="isInCall"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isMuted"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isOnHold"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="makeCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="peerProfile" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="sipSession" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="timeout" type="int">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="sendDtmf"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="code" type="int">
+</parameter>
+</method>
+<method name="sendDtmf"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="code" type="int">
+</parameter>
+<parameter name="result" type="android.os.Message">
+</parameter>
+</method>
+<method name="setListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.net.sip.SipAudioCall.Listener">
+</parameter>
+</method>
+<method name="setListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.net.sip.SipAudioCall.Listener">
+</parameter>
+<parameter name="callbackImmediately" type="boolean">
+</parameter>
+</method>
+<method name="setRingbackToneEnabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enabled" type="boolean">
+</parameter>
+</method>
+<method name="setRingtoneEnabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enabled" type="boolean">
+</parameter>
+</method>
+<method name="setSpeakerMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="speakerMode" type="boolean">
+</parameter>
+</method>
+<method name="startAudio"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="toggleMute"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="SipAudioCall.Listener"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SipAudioCall.Listener"
+ type="android.net.sip.SipAudioCall.Listener"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onCallBusy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+<method name="onCallEnded"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+<method name="onCallEstablished"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+<method name="onCallHeld"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+<method name="onCalling"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+<method name="onChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+<method name="onError"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+<parameter name="errorCode" type="int">
+</parameter>
+<parameter name="errorMessage" type="java.lang.String">
+</parameter>
+</method>
+<method name="onReadyToCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+<method name="onRinging"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+<parameter name="caller" type="android.net.sip.SipProfile">
+</parameter>
+</method>
+<method name="onRingingBack"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="call" type="android.net.sip.SipAudioCall">
+</parameter>
+</method>
+</class>
+<class name="SipErrorCode"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="toString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="errorCode" type="int">
+</parameter>
+</method>
+<field name="CLIENT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CROSS_DOMAIN_AUTHENTICATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-11"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DATA_CONNECTION_LOST"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INVALID_CREDENTIALS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INVALID_REMOTE_URI"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="IN_PROGRESS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NO_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PEER_NOT_REACHABLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SERVER_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SERVER_UNREACHABLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-12"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SOCKET_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TIME_OUT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSACTION_TERMINTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="SipException"
+ extends="java.lang.Exception"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SipException"
+ type="android.net.sip.SipException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SipException"
+ type="android.net.sip.SipException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="message" type="java.lang.String">
+</parameter>
+</constructor>
+<constructor name="SipException"
+ type="android.net.sip.SipException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="message" type="java.lang.String">
+</parameter>
+<parameter name="cause" type="java.lang.Throwable">
+</parameter>
+</constructor>
+</class>
+<class name="SipManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="createSipSession"
+ return="android.net.sip.SipSession"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfile" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipSession.Listener">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="getCallId"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="incomingCallIntent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="getOfferSessionDescription"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="incomingCallIntent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="getSessionFor"
+ return="android.net.sip.SipSession"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="incomingCallIntent" type="android.content.Intent">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="isApiSupported"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="isIncomingCallIntent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="isOpened"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="isRegistered"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="isSipWifiOnly"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="isVoipSupported"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="makeAudioCall"
+ return="android.net.sip.SipAudioCall"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfile" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="peerProfile" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipAudioCall.Listener">
+</parameter>
+<parameter name="timeout" type="int">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="makeAudioCall"
+ return="android.net.sip.SipAudioCall"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+<parameter name="peerProfileUri" type="java.lang.String">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipAudioCall.Listener">
+</parameter>
+<parameter name="timeout" type="int">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="newInstance"
+ return="android.net.sip.SipManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="open"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfile" type="android.net.sip.SipProfile">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="open"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfile" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="incomingCallPendingIntent" type="android.app.PendingIntent">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipRegistrationListener">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="register"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfile" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="expiryTime" type="int">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipRegistrationListener">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="setRegistrationListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipRegistrationListener">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="takeAudioCall"
+ return="android.net.sip.SipAudioCall"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="incomingCallIntent" type="android.content.Intent">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipAudioCall.Listener">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="takeAudioCall"
+ return="android.net.sip.SipAudioCall"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="incomingCallIntent" type="android.content.Intent">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipAudioCall.Listener">
+</parameter>
+<parameter name="ringtoneEnabled" type="boolean">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<method name="unregister"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfile" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="listener" type="android.net.sip.SipRegistrationListener">
+</parameter>
+<exception name="SipException" type="android.net.sip.SipException">
+</exception>
+</method>
+<field name="EXTRA_CALL_ID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android:sipCallID&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_OFFER_SD"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android:sipOfferSD&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INCOMING_CALL_RESULT_CODE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="101"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="SipProfile"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.lang.Cloneable">
+</implements>
+<implements name="android.os.Parcelable">
+</implements>
+<implements name="java.io.Serializable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAutoRegistration"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDisplayName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPassword"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPort"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getProfileName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getProtocol"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getProxyAddress"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSendKeepAlive"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSipDomain"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getUriString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getUserName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="out" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="SipProfile.Builder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SipProfile.Builder"
+ type="android.net.sip.SipProfile.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="profile" type="android.net.sip.SipProfile">
+</parameter>
+</constructor>
+<constructor name="SipProfile.Builder"
+ type="android.net.sip.SipProfile.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uriString" type="java.lang.String">
+</parameter>
+<exception name="ParseException" type="java.text.ParseException">
+</exception>
+</constructor>
+<constructor name="SipProfile.Builder"
+ type="android.net.sip.SipProfile.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="username" type="java.lang.String">
+</parameter>
+<parameter name="serverDomain" type="java.lang.String">
+</parameter>
+<exception name="ParseException" type="java.text.ParseException">
+</exception>
+</constructor>
+<method name="build"
+ return="android.net.sip.SipProfile"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setAutoRegistration"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="flag" type="boolean">
+</parameter>
+</method>
+<method name="setDisplayName"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="displayName" type="java.lang.String">
+</parameter>
+</method>
+<method name="setOutboundProxy"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="outboundProxy" type="java.lang.String">
+</parameter>
+</method>
+<method name="setPassword"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="password" type="java.lang.String">
+</parameter>
+</method>
+<method name="setPort"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="port" type="int">
+</parameter>
+<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
+</exception>
+</method>
+<method name="setProfileName"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
+<method name="setProtocol"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="protocol" type="java.lang.String">
+</parameter>
+<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
+</exception>
+</method>
+<method name="setSendKeepAlive"
+ return="android.net.sip.SipProfile.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="flag" type="boolean">
+</parameter>
+</method>
+</class>
+<interface name="SipRegistrationListener"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onRegistering"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+</method>
+<method name="onRegistrationDone"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+<parameter name="expiryTime" type="long">
+</parameter>
+</method>
+<method name="onRegistrationFailed"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="localProfileUri" type="java.lang.String">
+</parameter>
+<parameter name="errorCode" type="int">
+</parameter>
+<parameter name="errorMessage" type="java.lang.String">
+</parameter>
+</method>
+</interface>
+<class name="SipSession"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="answerCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="sessionDescription" type="java.lang.String">
+</parameter>
+<parameter name="timeout" type="int">
+</parameter>
+</method>
+<method name="changeCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="sessionDescription" type="java.lang.String">
+</parameter>
+<parameter name="timeout" type="int">
+</parameter>
+</method>
+<method name="endCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCallId"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getLocalIp"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getLocalProfile"
+ return="android.net.sip.SipProfile"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPeerProfile"
+ return="android.net.sip.SipProfile"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getState"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isInCall"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="makeCall"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callee" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="sessionDescription" type="java.lang.String">
+</parameter>
+<parameter name="timeout" type="int">
+</parameter>
+</method>
+<method name="register"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+</method>
+<method name="setListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.net.sip.SipSession.Listener">
+</parameter>
+</method>
+<method name="unregister"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="SipSession.Listener"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SipSession.Listener"
+ type="android.net.sip.SipSession.Listener"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onCallBusy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+</method>
+<method name="onCallChangeFailed"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="errorCode" type="int">
+</parameter>
+<parameter name="errorMessage" type="java.lang.String">
+</parameter>
+</method>
+<method name="onCallEnded"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+</method>
+<method name="onCallEstablished"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="sessionDescription" type="java.lang.String">
+</parameter>
+</method>
+<method name="onCalling"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+</method>
+<method name="onError"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="errorCode" type="int">
+</parameter>
+<parameter name="errorMessage" type="java.lang.String">
+</parameter>
+</method>
+<method name="onRegistering"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+</method>
+<method name="onRegistrationDone"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="duration" type="int">
+</parameter>
+</method>
+<method name="onRegistrationFailed"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="errorCode" type="int">
+</parameter>
+<parameter name="errorMessage" type="java.lang.String">
+</parameter>
+</method>
+<method name="onRegistrationTimeout"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+</method>
+<method name="onRinging"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+<parameter name="caller" type="android.net.sip.SipProfile">
+</parameter>
+<parameter name="sessionDescription" type="java.lang.String">
+</parameter>
+</method>
+<method name="onRingingBack"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="session" type="android.net.sip.SipSession">
+</parameter>
+</method>
+</class>
+<class name="SipSession.State"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="toString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="state" type="int">
+</parameter>
+</method>
+<field name="DEREGISTERING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INCOMING_CALL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INCOMING_CALL_ANSWERING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="IN_CALL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NOT_DEFINED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="101"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OUTGOING_CALL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OUTGOING_CALL_CANCELING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OUTGOING_CALL_RING_BACK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PINGING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="READY_TO_CALL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="REGISTERING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+</package>
 <package name="android.net.wifi"
 >
 <class name="ScanResult"
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index f6f80d1..a5b3e0e 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -936,3 +936,157 @@
 done:
     return 0;
 }
+
+int linklib(const char* dataDir, const char* asecLibDir)
+{
+    char libdir[PKG_PATH_MAX];
+    struct stat s, libStat;
+    int rc = 0;
+
+    const size_t libdirLen = strlen(dataDir) + strlen(PKG_LIB_POSTFIX);
+    if (libdirLen >= PKG_PATH_MAX) {
+        LOGE("library dir len too large");
+        rc = -1;
+        goto out;
+    }
+
+    if (snprintf(libdir, sizeof(libdir), "%s%s", dataDir, PKG_LIB_POSTFIX) != (ssize_t)libdirLen) {
+        LOGE("library dir not written successfully: %s\n", strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (stat(dataDir, &s) < 0) return -1;
+
+    if (chown(dataDir, 0, 0) < 0) {
+        LOGE("failed to chown '%s': %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    if (chmod(dataDir, 0700) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (lstat(libdir, &libStat) < 0) {
+        LOGE("couldn't stat lib dir: %s\n", strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (S_ISDIR(libStat.st_mode)) {
+        if (delete_dir_contents(libdir, 1, 0) < 0) {
+            rc = -1;
+            goto out;
+        }
+    } else if (S_ISLNK(libStat.st_mode)) {
+        if (unlink(libdir) < 0) {
+            rc = -1;
+            goto out;
+        }
+    }
+
+    if (symlink(asecLibDir, libdir) < 0) {
+        LOGE("couldn't symlink directory '%s' -> '%s': %s\n", libdir, asecLibDir, strerror(errno));
+        rc = -errno;
+        goto out;
+    }
+
+    if (lchown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
+        LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
+        unlink(libdir);
+        rc = -errno;
+        goto out;
+    }
+
+out:
+    if (chmod(dataDir, s.st_mode) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        return -errno;
+    }
+
+    if (chown(dataDir, s.st_uid, s.st_gid) < 0) {
+        LOGE("failed to chown '%s' : %s\n", dataDir, strerror(errno));
+        return -errno;
+    }
+
+    return rc;
+}
+
+int unlinklib(const char* dataDir)
+{
+    char libdir[PKG_PATH_MAX];
+    struct stat s, libStat;
+    int rc = 0;
+
+    const size_t libdirLen = strlen(dataDir) + strlen(PKG_LIB_POSTFIX);
+    if (libdirLen >= PKG_PATH_MAX) {
+        return -1;
+    }
+
+    if (snprintf(libdir, sizeof(libdir), "%s%s", dataDir, PKG_LIB_POSTFIX) != (ssize_t)libdirLen) {
+        LOGE("library dir not written successfully: %s\n", strerror(errno));
+        return -1;
+    }
+
+    if (stat(dataDir, &s) < 0) {
+        LOGE("couldn't state data dir");
+        return -1;
+    }
+
+    if (chown(dataDir, 0, 0) < 0) {
+        LOGE("failed to chown '%s': %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    if (chmod(dataDir, 0700) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (lstat(libdir, &libStat) < 0) {
+        LOGE("couldn't stat lib dir: %s\n", strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (S_ISDIR(libStat.st_mode)) {
+        if (delete_dir_contents(libdir, 1, 0) < 0) {
+            rc = -1;
+            goto out;
+        }
+    } else if (S_ISLNK(libStat.st_mode)) {
+        if (unlink(libdir) < 0) {
+            rc = -1;
+            goto out;
+        }
+    }
+
+    if (mkdir(libdir, 0755) < 0) {
+        LOGE("cannot create dir '%s': %s\n", libdir, strerror(errno));
+        rc = -errno;
+        goto out;
+    }
+
+    if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
+        LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
+        unlink(libdir);
+        rc = -errno;
+        goto out;
+    }
+
+out:
+    if (chmod(dataDir, s.st_mode) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    if (chown(dataDir, s.st_uid, s.st_gid) < 0) {
+        LOGE("failed to chown '%s' : %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    return rc;
+}
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index c991845..9ba6402 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -101,6 +101,16 @@
     return movefiles();
 }
 
+static int do_linklib(char **arg, char reply[REPLY_MAX])
+{
+    return linklib(arg[0], arg[1]);
+}
+
+static int do_unlinklib(char **arg, char reply[REPLY_MAX])
+{
+    return unlinklib(arg[0]);
+}
+
 struct cmdinfo {
     const char *name;
     unsigned numargs;
@@ -121,6 +131,8 @@
     { "getsize",              4, do_get_size },
     { "rmuserdata",           2, do_rm_user_data },
     { "movefiles",            0, do_movefiles },
+    { "linklib",              2, do_linklib },
+    { "unlinklib",            1, do_unlinklib },
 };
 
 static int readx(int s, void *_buf, int count)
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 479e4b2..59475e9 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -111,3 +111,5 @@
 int free_cache(int64_t free_size);
 int dexopt(const char *apk_path, uid_t uid, int is_public);
 int movefiles();
+int linklib(const char* target, const char* source);
+int unlinklib(const char* libPath);
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index 8e655e2..df1d960 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -67,10 +67,11 @@
     private static final int DISCONNECT_HFP_INCOMING = 6;
     public static final int DISCONNECT_A2DP_OUTGOING = 7;
     public static final int DISCONNECT_A2DP_INCOMING = 8;
+    public static final int DISCONNECT_PBAP_OUTGOING = 9;
 
-    public static final int UNPAIR = 9;
-    public static final int AUTO_CONNECT_PROFILES = 10;
-    public static final int TRANSITION_TO_STABLE = 11;
+    public static final int UNPAIR = 100;
+    public static final int AUTO_CONNECT_PROFILES = 101;
+    public static final int TRANSITION_TO_STABLE = 102;
 
     private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
 
@@ -84,7 +85,9 @@
     private BluetoothService mService;
     private BluetoothA2dpService mA2dpService;
     private BluetoothHeadset  mHeadsetService;
+    private BluetoothPbap     mPbapService;
     private boolean mHeadsetServiceConnected;
+    private boolean mPbapServiceConnected;
 
     private BluetoothDevice mDevice;
     private int mHeadsetState;
@@ -176,6 +179,7 @@
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
         HeadsetServiceListener l = new HeadsetServiceListener();
+        PbapServiceListener p = new PbapServiceListener();
     }
 
     private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
@@ -194,6 +198,22 @@
         }
     }
 
+    private class PbapServiceListener implements BluetoothPbap.ServiceListener {
+        public PbapServiceListener() {
+            mPbapService = new BluetoothPbap(mContext, this);
+        }
+        public void onServiceConnected() {
+            synchronized(BluetoothDeviceProfileState.this) {
+                mPbapServiceConnected = true;
+            }
+        }
+        public void onServiceDisconnected() {
+            synchronized(BluetoothDeviceProfileState.this) {
+                mPbapServiceConnected = false;
+            }
+        }
+    }
+
     private class BondedDevice extends HierarchicalState {
         @Override
         protected void enter() {
@@ -224,6 +244,9 @@
                 case DISCONNECT_A2DP_INCOMING:
                     transitionTo(mIncomingA2dp);
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
+                    processCommand(DISCONNECT_PBAP_OUTGOING);
+                    break;
                 case UNPAIR:
                     if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
                         sendMessage(DISCONNECT_HFP_OUTGOING);
@@ -342,6 +365,7 @@
                        deferMessage(deferMsg);
                     }
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -409,6 +433,7 @@
                     // If this causes incoming HFP to fail, it is more of a headset problem
                     // since both connections are incoming ones.
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -496,6 +521,7 @@
                 case DISCONNECT_A2DP_INCOMING:
                     // Ignore, will be handled by Bluez
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -561,6 +587,7 @@
                 case DISCONNECT_A2DP_INCOMING:
                     // Ignore, will be handled by Bluez
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -588,7 +615,7 @@
         }
     }
 
-    synchronized void deferHeadsetMessage(int command) {
+    synchronized void deferProfileServiceMessage(int command) {
         Message msg = new Message();
         msg.what = command;
         deferMessage(msg);
@@ -604,7 +631,7 @@
                 break;
             case CONNECT_HFP_INCOMING:
                 if (!mHeadsetServiceConnected) {
-                    deferHeadsetMessage(command);
+                    deferProfileServiceMessage(command);
                 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
                     return mHeadsetService.acceptIncomingConnect(mDevice);
                 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
@@ -621,8 +648,13 @@
                 return true;
             case DISCONNECT_HFP_OUTGOING:
                 if (!mHeadsetServiceConnected) {
-                    deferHeadsetMessage(command);
+                    deferProfileServiceMessage(command);
                 } else {
+                    // Disconnect PBAP
+                    // TODO(): Add PBAP to the state machine.
+                    Message m = new Message();
+                    m.what = DISCONNECT_PBAP_OUTGOING;
+                    deferMessage(m);
                     if (mHeadsetService.getPriority(mDevice) ==
                         BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
                         mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
@@ -645,6 +677,13 @@
                     return mA2dpService.disconnectSinkInternal(mDevice);
                 }
                 break;
+            case DISCONNECT_PBAP_OUTGOING:
+                if (!mPbapServiceConnected) {
+                    deferProfileServiceMessage(command);
+                } else {
+                    return mPbapService.disconnect();
+                }
+                break;
             case UNPAIR:
                 return mService.removeBondInternal(mDevice.getAddress());
             default:
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index af327c3..eb05d76 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -65,6 +65,18 @@
     public ApplicationInfo applicationInfo;
     
     /**
+     * The time at which the app was first installed.  Units are as
+     * per {@link System#currentTimeMillis()}.
+     */
+    public long firstInstallTime;
+
+    /**
+     * The time at which the app was last updated.  Units are as
+     * per {@link System#currentTimeMillis()}.
+     */
+    public long lastUpdateTime;
+
+    /**
      * All kernel group-IDs that have been assigned to this package.
      * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set.
      */
@@ -207,6 +219,8 @@
         } else {
             dest.writeInt(0);
         }
+        dest.writeLong(firstInstallTime);
+        dest.writeLong(lastUpdateTime);
         dest.writeIntArray(gids);
         dest.writeTypedArray(activities, parcelableFlags);
         dest.writeTypedArray(receivers, parcelableFlags);
@@ -242,6 +256,8 @@
         if (hasApp != 0) {
             applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
         }
+        firstInstallTime = source.readLong();
+        lastUpdateTime = source.readLong();
         gids = source.createIntArray();
         activities = source.createTypedArray(ActivityInfo.CREATOR);
         receivers = source.createTypedArray(ActivityInfo.CREATOR);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7346561..b5d1653 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1891,7 +1891,7 @@
         if (pkg == null) {
             return null;
         }
-        return PackageParser.generatePackageInfo(pkg, null, flags);
+        return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0);
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 89839ce..51f4202 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -187,7 +187,7 @@
      * @param flags indicating which optional information is included.
      */
     public static PackageInfo generatePackageInfo(PackageParser.Package p,
-            int gids[], int flags) {
+            int gids[], int flags, long firstInstallTime, long lastUpdateTime) {
 
         PackageInfo pi = new PackageInfo();
         pi.packageName = p.packageName;
@@ -197,6 +197,8 @@
         pi.sharedUserLabel = p.mSharedUserLabel;
         pi.applicationInfo = p.applicationInfo;
         pi.installLocation = p.installLocation;
+        pi.firstInstallTime = firstInstallTime;
+        pi.lastUpdateTime = lastUpdateTime;
         if ((flags&PackageManager.GET_GIDS) != 0) {
             pi.gids = gids;
         }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index ee57c6e..6260cdb 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2247,8 +2247,14 @@
                                 mTouchMode = TOUCH_MODE_OVERSCROLL;
                                 if (rawDeltaY > 0) {
                                     mEdgeGlowTop.onPull((float) overscroll / getHeight());
+                                    if (!mEdgeGlowBottom.isFinished()) {
+                                        mEdgeGlowBottom.onRelease();
+                                    }
                                 } else if (rawDeltaY < 0) {
                                     mEdgeGlowBottom.onPull((float) overscroll / getHeight());
+                                    if (!mEdgeGlowTop.isFinished()) {
+                                        mEdgeGlowTop.onRelease();
+                                    }
                                 }
                             }
                         }
@@ -2307,8 +2313,14 @@
                                         !contentFits())) {
                             if (rawDeltaY > 0) {
                                 mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight());
+                                if (!mEdgeGlowBottom.isFinished()) {
+                                    mEdgeGlowBottom.onRelease();
+                                }
                             } else if (rawDeltaY < 0) {
                                 mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight());
+                                if (!mEdgeGlowTop.isFinished()) {
+                                    mEdgeGlowTop.onRelease();
+                                }
                             }
                             invalidate();
                         }
diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java
index 1f7daab..7a990ad 100644
--- a/core/java/android/widget/EdgeGlow.java
+++ b/core/java/android/widget/EdgeGlow.java
@@ -91,6 +91,7 @@
     // How much dragging should effect the height of the glow image.
     // Number determined by user testing.
     private static final int PULL_DISTANCE_GLOW_FACTOR = 5;
+    private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f;
 
     private static final int VELOCITY_EDGE_FACTOR = 8;
     private static final int VELOCITY_GLOW_FACTOR = 16;
@@ -144,8 +145,10 @@
         mEdgeScaleY = mEdgeScaleYStart = Math.max(
                 HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
 
-        mGlowAlpha = mGlowAlphaStart = Math.max(
-                0.5f, Math.min(mGlowAlpha + Math.abs(deltaDistance), MAX_ALPHA));
+        mGlowAlpha = mGlowAlphaStart = Math.min(
+                mGlowAlpha +
+                (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR),
+                MAX_ALPHA);
 
         float glowChange = Math.abs(deltaDistance);
         if (deltaDistance > 0 && mPullDistance < 0) {
@@ -202,8 +205,8 @@
 
         // The edge should always be at least partially visible, regardless
         // of velocity.
-        mEdgeAlphaStart = 0.5f;
-        mEdgeScaleYStart = 0.2f;
+        mEdgeAlphaStart = 0.f;
+        mEdgeScaleY = mEdgeScaleYStart = 0.f;
         // The glow depends more on the velocity, and therefore starts out
         // nearly invisible.
         mGlowAlphaStart = 0.5f;
@@ -213,7 +216,8 @@
         // reflect the strength of the user's scrolling.
         mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
         // Edge should never get larger than the size of its asset.
-        mEdgeScaleYFinish = 1.f;
+        mEdgeScaleYFinish = Math.max(
+                HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f));
 
         // Growth for the size of the glow should be quadratic to properly
         // respond
@@ -281,10 +285,11 @@
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
 
+                    // After absorb, the glow and edge should fade to nothing.
                     mEdgeAlphaFinish = 0.f;
-                    mEdgeScaleYFinish = mEdgeScaleY;
+                    mEdgeScaleYFinish = 0.f;
                     mGlowAlphaFinish = 0.f;
-                    mGlowScaleYFinish = mGlowScaleY;
+                    mGlowScaleYFinish = 0.f;
                     break;
                 case STATE_PULL:
                     mState = STATE_PULL_DECAY;
@@ -296,14 +301,21 @@
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
 
-                    // After a pull, the glow should fade to nothing.
+                    // After pull, the glow and edge should fade to nothing.
                     mEdgeAlphaFinish = 0.f;
                     mEdgeScaleYFinish = 0.f;
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
                     break;
                 case STATE_PULL_DECAY:
-                    // Do nothing; wait for release
+                    // When receding, we want edge to decrease more slowly
+                    // than the glow.
+                    float factor = mGlowScaleYFinish != 0 ? 1
+                            / (mGlowScaleYFinish * mGlowScaleYFinish)
+                            : Float.MAX_VALUE;
+                    mEdgeScaleY = mEdgeScaleYStart +
+                        (mEdgeScaleYFinish - mEdgeScaleYStart) *
+                            interp * factor;
                     break;
                 case STATE_RECEDE:
                     mState = STATE_IDLE;
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 6a52f75..d38eef3 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -542,8 +542,14 @@
                         final int pulledToX = oldX + deltaX;
                         if (pulledToX < 0) {
                             mEdgeGlowLeft.onPull((float) deltaX / getWidth());
+                            if (!mEdgeGlowRight.isFinished()) {
+                                mEdgeGlowRight.onRelease();
+                            }
                         } else if (pulledToX > range) {
                             mEdgeGlowRight.onPull((float) deltaX / getWidth());
+                            if (!mEdgeGlowLeft.isFinished()) {
+                                mEdgeGlowLeft.onRelease();
+                            }
                         }
                     }
                 }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 0337b5c..1daf2ab 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -537,8 +537,14 @@
                         final int pulledToY = oldY + deltaY;
                         if (pulledToY < 0) {
                             mEdgeGlowTop.onPull((float) deltaY / getHeight());
+                            if (!mEdgeGlowBottom.isFinished()) {
+                                mEdgeGlowBottom.onRelease();
+                            }
                         } else if (pulledToY > range) {
                             mEdgeGlowBottom.onPull((float) deltaY / getHeight());
+                            if (!mEdgeGlowTop.isFinished()) {
+                                mEdgeGlowTop.onRelease();
+                            }
                         }
                     }
                 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5dd3d6b..d1974dc 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -92,11 +92,11 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.WindowManager;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.view.ViewRoot;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
@@ -4321,6 +4321,7 @@
 
         switch (keyCode) {
             case KeyEvent.KEYCODE_ENTER:
+                mEnterKeyIsDown = true;
                 // If ALT modifier is held, then we always insert a
                 // newline character.
                 if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) {
@@ -4353,6 +4354,7 @@
                 break;
                 
             case KeyEvent.KEYCODE_DPAD_CENTER:
+                mDPadCenterIsDown = true;
                 if (shouldAdvanceFocusOnEnter()) {
                     return 0;
                 }
@@ -4447,6 +4449,7 @@
 
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_CENTER:
+                mDPadCenterIsDown = false;
                 /*
                  * If there is a click listener, just call through to
                  * super, which will invoke it.
@@ -4467,6 +4470,7 @@
                 return super.onKeyUp(keyCode, event);
                 
             case KeyEvent.KEYCODE_ENTER:
+                mEnterKeyIsDown = false;
                 if (mInputContentType != null
                         && mInputContentType.onEditorActionListener != null
                         && mInputContentType.enterDown) {
@@ -7227,9 +7231,21 @@
         }
 
         // Two ints packed in a long
-        return (((long) start) << 32) | end;
+        return packRangeInLong(start, end);
     }
     
+    private static long packRangeInLong(int start, int end) {
+        return (((long) start) << 32) | end;
+    }
+
+    private static int extractRangeStartFromLong(long range) {
+        return (int) (range >>> 32);
+    }
+
+    private static int extractRangeEndFromLong(long range) {
+        return (int) (range & 0x00000000FFFFFFFFL);
+    }
+
     private void selectCurrentWord() {
         // In case selection mode is started after an orientation change or after a select all,
         // use the current selection instead of creating one
@@ -7237,24 +7253,31 @@
             return;
         }
 
-        int selectionStart, selectionEnd;
+        int minOffset, maxOffset;
 
-        // selectionModifierCursorController is not null at that point
-        SelectionModifierCursorController selectionModifierCursorController =
-            ((SelectionModifierCursorController) mSelectionModifierCursorController);
-        int minOffset = selectionModifierCursorController.getMinTouchOffset();
-        int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+        if (mDPadCenterIsDown || mEnterKeyIsDown) {
+            minOffset = getSelectionStart();
+            maxOffset = getSelectionEnd();
+        } else {
+            // selectionModifierCursorController is not null at that point
+            SelectionModifierCursorController selectionModifierCursorController =
+                ((SelectionModifierCursorController) mSelectionModifierCursorController);
+            minOffset = selectionModifierCursorController.getMinTouchOffset();
+            maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+        }
+
+        int selectionStart, selectionEnd;
         
         long wordLimits = getWordLimitsAt(minOffset);
         if (wordLimits >= 0) {
-            selectionStart = (int) (wordLimits >>> 32);
+            selectionStart = extractRangeStartFromLong(wordLimits);
         } else {
             selectionStart = Math.max(minOffset - 5, 0);
         }
 
         wordLimits = getWordLimitsAt(maxOffset);
         if (wordLimits >= 0) {
-            selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL);
+            selectionEnd = extractRangeEndFromLong(wordLimits);
         } else {
             selectionEnd = Math.min(maxOffset + 5, mText.length());
         }
@@ -7263,14 +7286,10 @@
     }
     
     private String getWordForDictionary() {
-        if (mLastTouchOffset < 0) {
-            return null;
-        }
-
-        long wordLimits = getWordLimitsAt(mLastTouchOffset);
+        long wordLimits = getWordLimitsAt(getSelectionStart());
         if (wordLimits >= 0) {
-            int start = (int) (wordLimits >>> 32);
-            int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
+            int start = extractRangeStartFromLong(wordLimits);
+            int end = extractRangeEndFromLong(wordLimits);
             return mTransformed.subSequence(start, end).toString();
         } else {
             return null;
@@ -7485,43 +7504,9 @@
                 CharSequence paste = clip.getText();
 
                 if (paste != null && paste.length() > 0) {
-                    // Paste adds/removes spaces before or after insertion as needed.
-
-                    if (Character.isSpaceChar(paste.charAt(0))) {
-                        if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                            // Two spaces at beginning of paste: remove one
-                            final int originalLength = mText.length();
-                            ((Editable) mText).replace(min - 1, min, "");
-                            // Due to filters, there is no garantee that exactly one character was
-                            // removed. Count instead.
-                            final int delta = mText.length() - originalLength;
-                            min += delta;
-                            max += delta;
-                        }
-                    } else {
-                        if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                            // No space at beginning of paste: add one
-                            final int originalLength = mText.length();
-                            ((Editable) mText).replace(min, min, " ");
-                            // Taking possible filters into account as above.
-                            final int delta = mText.length() - originalLength;
-                            min += delta;
-                            max += delta;
-                        }
-                    }
-
-                    if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
-                        if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
-                            // Two spaces at end of paste: remove one
-                            ((Editable) mText).replace(max, max + 1, "");
-                        }
-                    } else {
-                        if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
-                            // No space at end of paste: add one
-                            ((Editable) mText).replace(max, max, " ");
-                        }
-                    }
-
+                    long minMax = prepareSpacesAroundPaste(min, max, paste);
+                    min = extractRangeStartFromLong(minMax);
+                    max = extractRangeEndFromLong(minMax);
                     Selection.setSelection((Spannable) mText, max);
                     ((Editable) mText).replace(min, max, paste);
                     stopTextSelectionMode();
@@ -7557,6 +7542,49 @@
         return false;
     }
 
+    /**
+     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
+     * by [min, max] when replacing this region by paste.
+     */
+    private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
+        // Paste adds/removes spaces before or after insertion as needed.
+        if (Character.isSpaceChar(paste.charAt(0))) {
+            if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+                // Two spaces at beginning of paste: remove one
+                final int originalLength = mText.length();
+                ((Editable) mText).replace(min - 1, min, "");
+                // Due to filters, there is no garantee that exactly one character was
+                // removed. Count instead.
+                final int delta = mText.length() - originalLength;
+                min += delta;
+                max += delta;
+            }
+        } else {
+            if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+                // No space at beginning of paste: add one
+                final int originalLength = mText.length();
+                ((Editable) mText).replace(min, min, " ");
+                // Taking possible filters into account as above.
+                final int delta = mText.length() - originalLength;
+                min += delta;
+                max += delta;
+            }
+        }
+
+        if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
+            if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
+                // Two spaces at end of paste: remove one
+                ((Editable) mText).replace(max, max + 1, "");
+            }
+        } else {
+            if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
+                // No space at end of paste: add one
+                ((Editable) mText).replace(max, max, " ");
+            }
+        }
+        return packRangeInLong(min, max);
+    }
+
     @Override
     public boolean performLongClick() {
         if (super.performLongClick()) {
@@ -7860,8 +7888,7 @@
                 final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
                 final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY + mTouchOffsetY;
 
-                mController.updatePosition(this, (int) Math.round(newPosX),
-                        (int) Math.round(newPosY));
+                mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
 
                 break;
             }
@@ -8233,6 +8260,10 @@
     private CursorController        mSelectionModifierCursorController;
     private boolean                 mIsInTextSelectionMode = false;
     private int                     mLastTouchOffset = -1;
+    // These are needed to desambiguate a long click. If the long click comes from ones of these, we
+    // select from the current cursor position. Otherwise, select from long pressed position.
+    private boolean                 mDPadCenterIsDown = false;
+    private boolean                 mEnterKeyIsDown = false;
     // Created once and shared by different CursorController helper methods.
     // Only one cursor controller is active at any time which prevent race conditions.
     private static Rect             sCursorControllerTempRect = new Rect();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b9eb5d6..9d914ad 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -364,6 +364,13 @@
         android:description="@string/permdesc_nfcLlcp"
         android:label="@string/permlab_nfcLlcp" />
 
+    <!-- Allows an application to use SIP service -->
+    <permission android:name="android.permission.USE_SIP"
+        android:permissionGroup="android.permission-group.NETWORK"
+        android:protectionLevel="dangerous"
+        android:description="@string/permdesc_use_sip"
+        android:label="@string/permlab_use_sip" />
+
     <!-- Allows applications to call into AccountAuthenticators. Only
     the system can get this permission. -->
     <permission android:name="android.permission.ACCOUNT_MANAGER"
diff --git a/core/res/res/drawable-mdpi/ic_emergency.png b/core/res/res/drawable-mdpi/ic_emergency.png
index 45d0f21..c6faf1e 100755
--- a/core/res/res/drawable-mdpi/ic_emergency.png
+++ b/core/res/res/drawable-mdpi/ic_emergency.png
Binary files differ
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d9177e7..5c60fd5 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1240,6 +1240,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_cache_filesystem">Allows an application to read and write the cache filesystem.</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_use_sip">make/receive Internet calls</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_use_sip">Allows an application to use the SIP service to make/receive Internet calls.</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 276e281..d5f385b 100755
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -45,6 +45,7 @@
 import android.util.Log;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 
 public class PackageManagerTests extends AndroidTestCase {
@@ -378,6 +379,18 @@
                     assertEquals(publicSrcPath, appInstallPath);
                     assertFalse((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
                     assertTrue(info.nativeLibraryDir.startsWith(dataDir.getPath()));
+
+                    // Make sure the native library dir is not a symlink
+                    final File nativeLibDir = new File(info.nativeLibraryDir);
+                    assertTrue("Native library dir should exist at " + info.nativeLibraryDir,
+                            nativeLibDir.exists());
+                    try {
+                        assertEquals("Native library dir should not be a symlink",
+                                info.nativeLibraryDir,
+                                nativeLibDir.getCanonicalPath());
+                    } catch (IOException e) {
+                        fail("Can't read " + nativeLibDir.getPath());
+                    }
                 } else if (rLoc == INSTALL_LOC_SD){
                     assertTrue("Application flags (" + info.flags
                             + ") should contain FLAG_EXTERNAL_STORAGE",
@@ -391,6 +404,19 @@
                     assertTrue("The native library path (" + info.nativeLibraryDir
                             + ") should start with " + SECURE_CONTAINERS_PREFIX,
                             info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX));
+
+                    // Make sure the native library in /data/data/<app>/lib is a
+                    // symlink to the ASEC
+                    final File nativeLibSymLink = new File(info.dataDir, "lib");
+                    assertTrue("Native library symlink should exist at " + nativeLibSymLink.getPath(),
+                            nativeLibSymLink.exists());
+                    try {
+                        assertEquals(nativeLibSymLink.getPath() + " should be a symlink to "
+                                + info.nativeLibraryDir, info.nativeLibraryDir, nativeLibSymLink
+                                .getCanonicalPath());
+                    } catch (IOException e) {
+                        fail("Can't read " + nativeLibSymLink.getPath());
+                    }
                 } else {
                     // TODO handle error. Install should have failed.
                     fail("Install should have failed");
@@ -1406,13 +1432,21 @@
                         receiver);
                 assertTrue(retCode);
                 ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0);
-                assertNotNull(info);
+                assertNotNull("ApplicationInfo for recently installed application should exist",
+                        info);
                 if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) {
-                    assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0);
-                    assertTrue(info.nativeLibraryDir.startsWith(info.dataDir));
+                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should NOT be set",
+                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0);
+                    assertTrue("ApplicationInfo.nativeLibraryDir should start with " + info.dataDir,
+                            info.nativeLibraryDir.startsWith(info.dataDir));
                 } else if ((moveFlags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0){
-                    assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
-                    assertTrue(info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX));
+                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should be set",
+                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+                    assertTrue("ApplicationInfo.nativeLibraryDir should start with " + SECURE_CONTAINERS_PREFIX,
+                            info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX));
+                    final File nativeLibSymLink = new File(info.dataDir, "lib");
+                    assertTrue("The data directory should have a 'lib' symlink that points to the ASEC container",
+                            nativeLibSymLink.getCanonicalPath().startsWith(SECURE_CONTAINERS_PREFIX));
                 }
             }
         } catch (NameNotFoundException e) {
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 3662983..0521709 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -76,6 +76,7 @@
         virtual uint32_t    latency() const = 0;
         virtual float       msecsPerFrame() const = 0;
         virtual status_t    getPosition(uint32_t *position) = 0;
+        virtual int         getSessionId() = 0;
 
         // If no callback is specified, use the "write" API below to submit
         // audio data.
diff --git a/include/media/Metadata.h b/include/media/Metadata.h
index 241868a..9c915ce 100644
--- a/include/media/Metadata.h
+++ b/include/media/Metadata.h
@@ -91,6 +91,7 @@
     static const Type kPauseAvailable = 29;        // Boolean
     static const Type kSeekBackwardAvailable = 30; // Boolean
     static const Type kSeekForwardAvailable = 31;  // Boolean
+    static const Type kSeekAvailable = 32;         // Boolean
 
     // @param p[inout] The parcel to append the metadata records
     // to. The global metadata header should have been set already.
diff --git a/include/media/stagefright/MediaExtractor.h b/include/media/stagefright/MediaExtractor.h
index 21338ca..16b0a4c 100644
--- a/include/media/stagefright/MediaExtractor.h
+++ b/include/media/stagefright/MediaExtractor.h
@@ -45,13 +45,14 @@
     virtual sp<MetaData> getMetaData();
 
     enum Flags {
-        CAN_SEEK_BACKWARD  = 1,
-        CAN_SEEK_FORWARD   = 2,
+        CAN_SEEK_BACKWARD  = 1,  // the "seek 10secs back button"
+        CAN_SEEK_FORWARD   = 2,  // the "seek 10secs forward button"
         CAN_PAUSE          = 4,
+        CAN_SEEK           = 8,  // the "seek bar"
     };
 
     // If subclasses do _not_ override this, the default is
-    // CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_PAUSE
+    // CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK | CAN_PAUSE
     virtual uint32_t flags() const;
 
 protected:
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 43042c0b..ef19579 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -80,9 +80,16 @@
         }
     }
 
+    private Intent createInstallIntent() {
+        Intent intent = new Intent(INSTALL_ACTION);
+        intent.setClassName("com.android.certinstaller",
+                "com.android.certinstaller.CertInstallerMain");
+        return intent;
+    }
+
     public void install(Context context, KeyPair pair) {
         try {
-            Intent intent = new Intent(INSTALL_ACTION);
+            Intent intent = createInstallIntent();
             intent.putExtra(PRIVATE_KEY, pair.getPrivate().getEncoded());
             intent.putExtra(PUBLIC_KEY, pair.getPublic().getEncoded());
             context.startActivity(intent);
@@ -93,7 +100,7 @@
 
     public void install(Context context, String type, byte[] value) {
         try {
-            Intent intent = new Intent(INSTALL_ACTION);
+            Intent intent = createInstallIntent();
             intent.putExtra(type, value);
             context.startActivity(intent);
         } catch (ActivityNotFoundException e) {
@@ -103,7 +110,7 @@
 
     public void installFromSdCard(Context context) {
         try {
-            context.startActivity(new Intent(INSTALL_ACTION));
+            context.startActivity(createInstallIntent());
         } catch (ActivityNotFoundException e) {
             Log.w(LOGTAG, e.toString());
         }
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 8e173aa..7adc764 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -2447,7 +2447,7 @@
         yPrecision = mLocked.orientedYPrecision;
     } // release lock
 
-    getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TOUCHSCREEN, policyFlags,
+    getDispatcher()->notifyMotion(when, getDeviceId(), getSources(), policyFlags,
             motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
             pointerCount, pointerIds, pointerCoords,
             xPrecision, yPrecision, mDownTime);
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index bd25da2..8d408c2 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -102,8 +102,9 @@
     public static final int PAUSE_AVAILABLE = 29;         // Boolean
     public static final int SEEK_BACKWARD_AVAILABLE = 30; // Boolean
     public static final int SEEK_FORWARD_AVAILABLE = 31;  // Boolean
+    public static final int SEEK_AVAILABLE = 32;          // Boolean
 
-    private static final int LAST_SYSTEM = 31;
+    private static final int LAST_SYSTEM = 32;
     private static final int FIRST_CUSTOM = 8192;
 
     // Shorthands to set the MediaPlayer's metadata filter.
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 6f94e8b..49e5e89 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -727,7 +727,7 @@
 }
 
 static void android_media_MediaPlayer_attachAuxEffect(JNIEnv *env,  jobject thiz, jint effectId) {
-    LOGV("attachAuxEffect(): %d", sessionId);
+    LOGV("attachAuxEffect(): %d", effectId);
     sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
     if (mp == NULL ) {
         jniThrowException(env, "java/lang/IllegalStateException", NULL);
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index c6b2efb..cc41e66 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -56,6 +56,7 @@
     mVideoWidth = mVideoHeight = 0;
     mLockThreadId = 0;
     mAudioSessionId = AudioSystem::newAudioSessionId();
+    mSendLevel = 0;
 }
 
 MediaPlayer::~MediaPlayer()
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index d975cb9..c43e9bb 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1546,6 +1546,11 @@
 
 }
 
+int MediaPlayerService::AudioOutput::getSessionId()
+{
+    return mSessionId;
+}
+
 #undef LOG_TAG
 #define LOG_TAG "AudioCache"
 MediaPlayerService::AudioCache::AudioCache(const char* name) :
@@ -1733,4 +1738,9 @@
     p->mSignal.signal();
 }
 
+int MediaPlayerService::AudioCache::getSessionId()
+{
+    return 0;
+}
+
 } // namespace android
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index a967ee2..4492e20 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -77,6 +77,7 @@
         virtual uint32_t        latency() const;
         virtual float           msecsPerFrame() const;
         virtual status_t        getPosition(uint32_t *position);
+        virtual int             getSessionId();
 
         virtual status_t        open(
                 uint32_t sampleRate, int channelCount,
@@ -133,6 +134,7 @@
         virtual uint32_t        latency() const;
         virtual float           msecsPerFrame() const;
         virtual status_t        getPosition(uint32_t *position);
+        virtual int             getSessionId();
 
         virtual status_t        open(
                 uint32_t sampleRate, int channelCount, int format,
diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp
index 2c96d6d..6bded09 100644
--- a/media/libmediaplayerservice/StagefrightPlayer.cpp
+++ b/media/libmediaplayerservice/StagefrightPlayer.cpp
@@ -180,6 +180,10 @@
             Metadata::kSeekForwardAvailable,
             flags & MediaExtractor::CAN_SEEK_FORWARD);
 
+    metadata.appendBool(
+            Metadata::kSeekAvailable,
+            flags & MediaExtractor::CAN_SEEK);
+
     return OK;
 }
 
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
index 71d48b3..c0b1abe 100644
--- a/media/libstagefright/AMRWriter.cpp
+++ b/media/libstagefright/AMRWriter.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <media/stagefright/AMRWriter.h>
-
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
@@ -23,6 +22,8 @@
 #include <media/stagefright/MediaSource.h>
 #include <media/stagefright/MetaData.h>
 #include <media/mediarecorder.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
 
 namespace android {
 
@@ -194,6 +195,7 @@
     int64_t maxTimestampUs = 0;
     status_t err = OK;
 
+    prctl(PR_SET_NAME, (unsigned long)"AMRWriter", 0, 0, 0);
     while (!mDone) {
         MediaBuffer *buffer;
         err = mSource->read(&buffer);
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 8c17aab..53543b3 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -868,9 +868,7 @@
 }
 
 status_t AwesomePlayer::seekTo(int64_t timeUs) {
-    if (mExtractorFlags
-            & (MediaExtractor::CAN_SEEK_FORWARD
-                | MediaExtractor::CAN_SEEK_BACKWARD)) {
+    if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
         Mutex::Autolock autoLock(mLock);
         return seekTo_l(timeUs);
     }
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 6d00d7c..e53b0a0 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -20,8 +20,9 @@
 
 #include <arpa/inet.h>
 
-#include <ctype.h>
 #include <pthread.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
 
 #include <media/stagefright/MPEG4Writer.h>
 #include <media/stagefright/MediaBuffer.h>
@@ -1104,6 +1105,7 @@
 void MPEG4Writer::threadFunc() {
     LOGV("threadFunc");
 
+    prctl(PR_SET_NAME, (unsigned long)"MPEG4Writer", 0, 0, 0);
     while (!mDone) {
         {
             Mutex::Autolock autolock(mLock);
@@ -1632,6 +1634,11 @@
     int64_t previousPausedDurationUs = 0;
     int64_t timestampUs;
 
+    if (mIsAudio) {
+        prctl(PR_SET_NAME, (unsigned long)"AudioTrackEncoding", 0, 0, 0);
+    } else {
+        prctl(PR_SET_NAME, (unsigned long)"VideoTrackEncoding", 0, 0, 0);
+    }
     sp<MetaData> meta_data;
 
     mNumSamples = 0;
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index 9bc94de..8a5fb11 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -41,7 +41,7 @@
 }
 
 uint32_t MediaExtractor::flags() const {
-    return CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_PAUSE;
+    return CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_PAUSE | CAN_SEEK;
 }
 
 // static
diff --git a/media/libstagefright/avc_utils.cpp b/media/libstagefright/avc_utils.cpp
index a8f1104..8762d29 100644
--- a/media/libstagefright/avc_utils.cpp
+++ b/media/libstagefright/avc_utils.cpp
@@ -41,10 +41,12 @@
     br.skipBits(16);
     parseUE(&br);  // seq_parameter_set_id
 
+    unsigned chroma_format_idc = 1;  // 4:2:0 chroma format
+
     if (profile_idc == 100 || profile_idc == 110
             || profile_idc == 122 || profile_idc == 244
             || profile_idc == 44 || profile_idc == 83 || profile_idc == 86) {
-        unsigned chroma_format_idc = parseUE(&br);
+        chroma_format_idc = parseUE(&br);
         if (chroma_format_idc == 3) {
             br.skipBits(1);  // residual_colour_transform_flag
         }
@@ -85,6 +87,41 @@
 
     *height = (2 - frame_mbs_only_flag)
         * (pic_height_in_map_units_minus1 * 16 + 16);
+
+    if (!frame_mbs_only_flag) {
+        br.getBits(1);  // mb_adaptive_frame_field_flag
+    }
+
+    br.getBits(1);  // direct_8x8_inference_flag
+
+    if (br.getBits(1)) {  // frame_cropping_flag
+        unsigned frame_crop_left_offset = parseUE(&br);
+        unsigned frame_crop_right_offset = parseUE(&br);
+        unsigned frame_crop_top_offset = parseUE(&br);
+        unsigned frame_crop_bottom_offset = parseUE(&br);
+
+        unsigned cropUnitX, cropUnitY;
+        if (chroma_format_idc == 0  /* monochrome */) {
+            cropUnitX = 1;
+            cropUnitY = 2 - frame_mbs_only_flag;
+        } else {
+            unsigned subWidthC = (chroma_format_idc == 3) ? 1 : 2;
+            unsigned subHeightC = (chroma_format_idc == 1) ? 2 : 1;
+
+            cropUnitX = subWidthC;
+            cropUnitY = subHeightC * (2 - frame_mbs_only_flag);
+        }
+
+        LOGV("frame_crop = (%u, %u, %u, %u), cropUnitX = %u, cropUnitY = %u",
+             frame_crop_left_offset, frame_crop_right_offset,
+             frame_crop_top_offset, frame_crop_bottom_offset,
+             cropUnitX, cropUnitY);
+
+        *width -=
+            (frame_crop_left_offset + frame_crop_right_offset) * cropUnitX;
+        *height -=
+            (frame_crop_top_offset + frame_crop_bottom_offset) * cropUnitY;
+    }
 }
 
 }  // namespace android
diff --git a/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.cpp b/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.cpp
index 35b6475..286c636 100644
--- a/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.cpp
+++ b/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.cpp
@@ -40,7 +40,7 @@
     int32 xr[ ],                rescaled data
     struct gr_info_s *gr_info,  granule structure
     mp3Header *info,            mp3 header info
-    int32  Scratch_mem[168]     for temporary usage
+    int32  Scratch_mem[198]     for temporary usage
 
  Outputs:
 
@@ -120,7 +120,7 @@
                    granuleInfo *gr_info,
                    int32  *used_freq_lines,
                    mp3Header *info,
-                   int32  Scratch_mem[168])
+                   int32  Scratch_mem[198])
 {
     int32 sfreq =  info->version_x + (info->version_x << 1);
     sfreq += info->sampling_frequency;
diff --git a/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.h b/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.h
index ba6ec16..5248951 100644
--- a/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.h
+++ b/media/libstagefright/codecs/mp3dec/src/pvmp3_reorder.h
@@ -89,7 +89,7 @@
     granuleInfo *gr_info,
     int32 *used_freq_lines,
     mp3Header *info,
-    int32  Scratch_mem[168]);
+    int32  Scratch_mem[198]);
 
 #ifdef __cplusplus
 }
diff --git a/media/libstagefright/codecs/mp3dec/src/s_tmp3dec_file.h b/media/libstagefright/codecs/mp3dec/src/s_tmp3dec_file.h
index 805cedb..611e08f 100644
--- a/media/libstagefright/codecs/mp3dec/src/s_tmp3dec_file.h
+++ b/media/libstagefright/codecs/mp3dec/src/s_tmp3dec_file.h
@@ -87,7 +87,7 @@
         int32           num_channels;
         int32           predicted_frame_size;
         int32           frame_start;
-        int32           Scratch_mem[168];
+        int32           Scratch_mem[198];
         tmp3dec_chan    perChan[CHAN];
         mp3ScaleFactors scaleFactors[CHAN];
         mp3SideInfo     sideInfo;
diff --git a/media/libstagefright/include/ARTSPController.h b/media/libstagefright/include/ARTSPController.h
index 300d8f7..c2f3090 100644
--- a/media/libstagefright/include/ARTSPController.h
+++ b/media/libstagefright/include/ARTSPController.h
@@ -46,6 +46,14 @@
 
     void onMessageReceived(const sp<AMessage> &msg);
 
+    virtual uint32_t flags() const {
+        // Seeking 10secs forward or backward is a very expensive operation
+        // for rtsp, so let's not enable that.
+        // The user can always use the seek bar.
+
+        return CAN_PAUSE | CAN_SEEK;
+    }
+
 protected:
     virtual ~ARTSPController();
 
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index a31b2b2..2c9cfd3 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -86,6 +86,7 @@
           mFirstAccessUnitNTP(0),
           mNumAccessUnitsReceived(0),
           mCheckPending(false),
+          mCheckGeneration(0),
           mTryTCPInterleaving(false),
           mReceivedFirstRTCPPacket(false) {
         mNetLooper->setName("rtsp net");
@@ -434,6 +435,13 @@
 
             case 'chek':
             {
+                int32_t generation;
+                CHECK(msg->findInt32("generation", &generation));
+                if (generation != mCheckGeneration) {
+                    // This is an outdated message. Ignore.
+                    break;
+                }
+
                 if (mNumAccessUnitsReceived == 0) {
                     LOGI("stream ended? aborting.");
                     (new AMessage('abor', id()))->post();
@@ -454,12 +462,7 @@
                 }
 
                 ++mNumAccessUnitsReceived;
-
-                if (!mCheckPending) {
-                    mCheckPending = true;
-                    sp<AMessage> check = new AMessage('chek', id());
-                    check->post(kAccessUnitTimeoutUs);
-                }
+                postAccessUnitTimeoutCheck();
 
                 size_t trackIndex;
                 CHECK(msg->findSize("track-index", &trackIndex));
@@ -557,6 +560,11 @@
 
                 mSeekPending = true;
 
+                // Disable the access unit timeout until we resumed
+                // playback again.
+                mCheckPending = true;
+                ++mCheckGeneration;
+
                 AString request = "PAUSE ";
                 request.append(mSessionURL);
                 request.append(" RTSP/1.0\r\n");
@@ -612,6 +620,9 @@
                 LOGI("PLAY completed with result %d (%s)",
                      result, strerror(-result));
 
+                mCheckPending = false;
+                postAccessUnitTimeoutCheck();
+
                 if (result == OK) {
                     sp<RefBase> obj;
                     CHECK(msg->findObject("response", &obj));
@@ -674,6 +685,17 @@
         }
     }
 
+    void postAccessUnitTimeoutCheck() {
+        if (mCheckPending) {
+            return;
+        }
+
+        mCheckPending = true;
+        sp<AMessage> check = new AMessage('chek', id());
+        check->setInt32("generation", mCheckGeneration);
+        check->post(kAccessUnitTimeoutUs);
+    }
+
     static void SplitString(
             const AString &s, const char *separator, List<AString> *items) {
         items->clear();
@@ -783,6 +805,7 @@
     uint64_t mFirstAccessUnitNTP;
     int64_t mNumAccessUnitsReceived;
     bool mCheckPending;
+    int32_t mCheckGeneration;
     bool mTryTCPInterleaving;
     bool mReceivedFirstRTCPPacket;
 
diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java
index 1f34eba..85eca60 100644
--- a/services/java/com/android/server/Installer.java
+++ b/services/java/com/android/server/Installer.java
@@ -327,4 +327,33 @@
     public int moveFiles() {
         return execute("movefiles");
     }
+
+    public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath) {
+        if (dataPath == null) {
+            Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
+            return -1;
+        } else if (nativeLibPath == null) {
+            Slog.e(TAG, "unlinkNativeLibraryDirectory nativeLibPath is null");
+            return -1;
+        }
+
+        StringBuilder builder = new StringBuilder("linklib ");
+        builder.append(dataPath);
+        builder.append(' ');
+        builder.append(nativeLibPath);
+
+        return execute(builder.toString());
+    }
+
+    public int unlinkNativeLibraryDirectory(String dataPath) {
+        if (dataPath == null) {
+            Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
+            return -1;
+        }
+
+        StringBuilder builder = new StringBuilder("unlinklib ");
+        builder.append(dataPath);
+
+        return execute(builder.toString());
+    }
 }
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 361cd3b..84024b8 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -130,6 +130,7 @@
 
     // Handler messages
     private static final int MESSAGE_LOCATION_CHANGED = 1;
+    private static final int MESSAGE_PACKAGE_UPDATED = 2;
 
     // wakelock variables
     private final static String WAKELOCK_KEY = "LocationManagerService";
@@ -1826,6 +1827,19 @@
                             handleLocationChangedLocked(location, passive);
                         }
                     }
+                } else if (msg.what == MESSAGE_PACKAGE_UPDATED) {
+                    String packageName = (String) msg.obj;
+                    String packageDot = packageName + ".";
+
+                    // reconnect to external providers after their packages have been updated
+                    if (mNetworkLocationProvider != null &&
+                        mNetworkLocationProviderPackageName.startsWith(packageDot)) {
+                        mNetworkLocationProvider.reconnect();
+                    }
+                    if (mGeocodeProvider != null &&
+                        mGeocodeProviderPackageName.startsWith(packageDot)) {
+                        mGeocodeProvider.reconnect();
+                    }
                 }
             } catch (Exception e) {
                 // Log, don't crash!
@@ -1928,17 +1942,8 @@
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onPackageUpdateFinished(String packageName, int uid) {
-            String packageDot = packageName + ".";
-
-            // reconnect to external providers after their packages have been updated
-            if (mNetworkLocationProvider != null &&
-                    mNetworkLocationProviderPackageName.startsWith(packageDot)) {
-                mNetworkLocationProvider.reconnect();
-            }
-            if (mGeocodeProvider != null &&
-                    mGeocodeProviderPackageName.startsWith(packageDot)) {
-                mGeocodeProvider.reconnect();
-            }
+            // Called by main thread; divert work to LocationWorker.
+            Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget();
         }
     };
 
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 050e0c8..ce0e79f 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -183,6 +183,7 @@
     static final int SCAN_UPDATE_SIGNATURE = 1<<3;
     static final int SCAN_NEW_INSTALL = 1<<4;
     static final int SCAN_NO_PATHS = 1<<5;
+    static final int SCAN_UPDATE_TIME = 1<<6;
 
     static final int REMOVE_CHATTY = 1<<16;
     
@@ -921,7 +922,7 @@
             mFrameworkInstallObserver.startWatching();
             scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM
                     | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanMode | SCAN_NO_DEX);
+                    scanMode | SCAN_NO_DEX, 0);
             
             // Collect all system packages.
             mSystemAppDir = new File(Environment.getRootDirectory(), "app");
@@ -929,7 +930,7 @@
                 mSystemAppDir.getPath(), OBSERVER_EVENTS, true);
             mSystemInstallObserver.startWatching();
             scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode);
+                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
             
             // Collect all vendor packages.
             mVendorAppDir = new File("/vendor/app");
@@ -937,7 +938,7 @@
                 mVendorAppDir.getPath(), OBSERVER_EVENTS, true);
             mVendorInstallObserver.startWatching();
             scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode);
+                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
 
             if (mInstaller != null) {
                 if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
@@ -984,12 +985,13 @@
             mAppInstallObserver = new AppDirObserver(
                 mAppInstallDir.getPath(), OBSERVER_EVENTS, false);
             mAppInstallObserver.startWatching();
-            scanDirLI(mAppInstallDir, 0, scanMode);
+            scanDirLI(mAppInstallDir, 0, scanMode, 0);
 
             mDrmAppInstallObserver = new AppDirObserver(
                 mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false);
             mDrmAppInstallObserver.startWatching();
-            scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, scanMode);
+            scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
+                    scanMode, 0);
 
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                     SystemClock.uptimeMillis());
@@ -1317,14 +1319,15 @@
     PackageInfo generatePackageInfo(PackageParser.Package p, int flags) {
         if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
             // The package has been uninstalled but has retained data and resources.
-            return PackageParser.generatePackageInfo(p, null, flags);
+            return PackageParser.generatePackageInfo(p, null, flags, 0, 0);
         }
         final PackageSetting ps = (PackageSetting)p.mExtras;
         if (ps == null) {
             return null;
         }
         final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
-        return PackageParser.generatePackageInfo(p, gp.gids, flags);
+        return PackageParser.generatePackageInfo(p, gp.gids, flags,
+                ps.firstInstallTime, ps.lastUpdateTime);
     }
 
     public PackageInfo getPackageInfo(String packageName, int flags) {
@@ -2483,7 +2486,7 @@
         return finalList;
     }
 
-    private void scanDirLI(File dir, int flags, int scanMode) {
+    private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {
         String[] files = dir.list();
         if (files == null) {
             Log.d(TAG, "No files in app dir " + dir);
@@ -2500,7 +2503,7 @@
                 continue;
             }
             PackageParser.Package pkg = scanPackageLI(file,
-                    flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
+                    flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime);
             // Don't mess around with apps in system partition.
             if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                     mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
@@ -2568,7 +2571,7 @@
      *  Returns null in case of errors and the error code is stored in mLastScanError
      */
     private PackageParser.Package scanPackageLI(File scanFile,
-            int parseFlags, int scanMode) {
+            int parseFlags, int scanMode, long currentTime) {
         mLastScanError = PackageManager.INSTALL_SUCCEEDED;
         String scanPath = scanFile.getPath();
         parseFlags |= mDefParseFlags;
@@ -2668,7 +2671,7 @@
         // Set application objects path explicitly.
         setApplicationInfoPaths(pkg, codePath, resPath);
         // Note that we invoke the following method only if we are about to unpack an application
-        return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
+        return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime);
     }
 
     private static void setApplicationInfoPaths(PackageParser.Package pkg, String destCodePath,
@@ -2799,7 +2802,7 @@
     }
     
     private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
-            int parseFlags, int scanMode) {
+            int parseFlags, int scanMode, long currentTime) {
         File scanFile = new File(pkg.mScanPath);
         if (scanFile == null || pkg.applicationInfo.sourceDir == null ||
                 pkg.applicationInfo.publicSourceDir == null) {
@@ -3132,7 +3135,7 @@
             }
         }
         
-        long scanFileTime = scanFile.lastModified();
+        final long scanFileTime = scanFile.lastModified();
         final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0;
         final boolean scanFileNewer = forceDex || scanFileTime != pkgSetting.timeStamp;
         pkg.applicationInfo.processName = fixProcessName(
@@ -3290,7 +3293,11 @@
                     }
                 } else if (!isExternal(pkg)) {
                     Log.i(TAG, path + " changed; unpacking");
+                    mInstaller.unlinkNativeLibraryDirectory(dataPath.getPath());
                     NativeLibraryHelper.copyNativeBinariesLI(scanFile, sharedLibraryDir);
+                } else {
+                    mInstaller.linkNativeLibraryDirectory(dataPath.getPath(),
+                            pkg.applicationInfo.nativeLibraryDir);
                 }
             }
             pkg.mScanPath = path;
@@ -3328,6 +3335,24 @@
             // Make sure we don't accidentally delete its data.
             mSettings.mPackagesToBeCleaned.remove(pkgName);
             
+            // Take care of first install / last update times.
+            if (currentTime != 0) {
+                if (pkgSetting.firstInstallTime == 0) {
+                    pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
+                } else if ((scanMode&SCAN_UPDATE_TIME) != 0) {
+                    pkgSetting.lastUpdateTime = currentTime;
+                }
+            } else if (pkgSetting.firstInstallTime == 0) {
+                // We need *something*.  Take time time stamp of the file.
+                pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
+            } else if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+                if (scanFileTime != pkgSetting.timeStamp) {
+                    // A package on the system image has changed; consider this
+                    // to be an update.
+                    pkgSetting.lastUpdateTime = scanFileTime;
+                }
+            }
+
             int N = pkg.providers.size();
             StringBuilder r = null;
             int i;
@@ -4467,7 +4492,8 @@
                                         | PackageParser.PARSE_IS_SYSTEM_DIR: 0) |
                                 PackageParser.PARSE_CHATTY |
                                 PackageParser.PARSE_MUST_BE_APK,
-                                SCAN_MONITOR | SCAN_NO_PATHS);
+                                SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
+                                System.currentTimeMillis());
                         if (p != null) {
                             synchronized (mPackages) {
                                 updatePermissionsLP(p.packageName, p,
@@ -5010,10 +5036,6 @@
                 try { if (out != null) out.close(); } catch (IOException e) {}
             }
 
-            if (!temp) {
-                NativeLibraryHelper.copyNativeBinariesLI(codeFile, new File(libraryPath));
-            }
-
             return ret;
         }
 
@@ -5460,7 +5482,8 @@
             }
         }
         mLastScanError = PackageManager.INSTALL_SUCCEEDED;
-        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode);
+        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
+                System.currentTimeMillis());
         if (newPackage == null) {
             Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
             if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
@@ -5523,16 +5546,24 @@
             oldInstallerPackageName = mSettings.getInstallerPackageName(pkgName);
         }
 
+        long origUpdateTime;
+        if (pkg.mExtras != null) {
+            origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime;
+        } else {
+            origUpdateTime = 0;
+        }
+
         // First delete the existing package while retaining the data directory
         if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA,
                 res.removedInfo, true)) {
-            // If the existing package was'nt successfully deleted
+            // If the existing package wasn't successfully deleted
             res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
             deletedPkg = false;
         } else {
             // Successfully deleted the old package. Now proceed with re-installation
             mLastScanError = PackageManager.INSTALL_SUCCEEDED;
-            newPackage = scanPackageLI(pkg, parseFlags, scanMode);
+            newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME,
+                    System.currentTimeMillis());
             if (newPackage == null) {
                 Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
                 if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
@@ -5570,8 +5601,10 @@
                 int oldParseFlags  = mDefParseFlags | PackageParser.PARSE_CHATTY |
                         (isForwardLocked(deletedPackage) ? PackageParser.PARSE_FORWARD_LOCK : 0) |
                         (oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0);
-                int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE;
-                if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode) == null) {
+                int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE
+                        | SCAN_UPDATE_TIME;
+                if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode,
+                        origUpdateTime) == null) {
                     Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade");
                     return;
                 }
@@ -5622,13 +5655,18 @@
         // Successfully disabled the old package. Now proceed with re-installation
         mLastScanError = PackageManager.INSTALL_SUCCEEDED;
         pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-        newPackage = scanPackageLI(pkg, parseFlags, scanMode);
+        newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0);
         if (newPackage == null) {
             Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
             if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
                 res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
             }
         } else {
+            if (newPackage.mExtras != null) {
+                final PackageSetting newPkgSetting = (PackageSetting)newPackage.mExtras;
+                newPkgSetting.firstInstallTime = oldPkgSetting.firstInstallTime;
+                newPkgSetting.lastUpdateTime = System.currentTimeMillis();
+            }
             updateSettingsLI(newPackage, installerPackageName, res);
             updatedSettings = true;
         }
@@ -5640,12 +5678,10 @@
                 removePackageLI(newPackage, true);
             }
             // Add back the old system package
-            scanPackageLI(oldPkg, parseFlags,
-                    SCAN_MONITOR
-                    | SCAN_UPDATE_SIGNATURE);
+            scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0);
             // Restore the old system information in Settings
             synchronized(mPackages) {
-                if(updatedSettings) {
+                if (updatedSettings) {
                     mSettings.enableSystemPackageLP(packageName);
                     mSettings.setInstallerPackageName(packageName,
                             oldPkgSetting.installerPackageName);
@@ -5659,7 +5695,6 @@
                 PackageSetting ps = mSettings.getDisabledSystemPkg(packageName);
                 if (ps != null && ps.codePathString != null &&
                         !ps.codePathString.equals(oldPkgSetting.codePathString)) {
-                    int installFlags = 0;
                     res.removedInfo.args = createInstallArgs(0, oldPkgSetting.codePathString,
                             oldPkgSetting.resourcePathString, oldPkgSetting.nativeLibraryPathString);
                 }
@@ -6194,7 +6229,7 @@
         // Install the system package
         PackageParser.Package newPkg = scanPackageLI(ps.codePath,
                 PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
-                SCAN_MONITOR | SCAN_NO_PATHS);
+                SCAN_MONITOR | SCAN_NO_PATHS, 0);
 
         if (newPkg == null) {
             Slog.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError);
@@ -7047,6 +7082,8 @@
             printedSomething = false;
             SharedUserSetting packageSharedUser = null;
             if (dumpStar || dumpPackages) {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                Date date = new Date();
                 for (PackageSetting ps : mSettings.mPackages.values()) {
                     if (packageName != null && !packageName.equals(ps.realName)
                             && !packageName.equals(ps.name)) {
@@ -7121,7 +7158,12 @@
                         }
                     }
                     pw.println("]");
-                    pw.print("    timeStamp="); pw.println(String.valueOf(ps.timeStamp));
+                    pw.print("    timeStamp=");
+                            date.setTime(ps.timeStamp); pw.println(sdf.format(date));
+                    pw.print("    firstInstallTime=");
+                            date.setTime(ps.firstInstallTime); pw.println(sdf.format(date));
+                    pw.print("    lastUpdateTime=");
+                            date.setTime(ps.lastUpdateTime); pw.println(sdf.format(date));
                     pw.print("    signatures="); pw.println(ps.signatures);
                     pw.print("    permissionsFixed="); pw.print(ps.permissionsFixed);
                             pw.print(" haveGids="); pw.println(ps.haveGids);
@@ -7632,6 +7674,8 @@
         String nativeLibraryPathString;
         String obbPathString;
         long timeStamp;
+        long firstInstallTime;
+        long lastUpdateTime;
         int versionCode;
 
         boolean uidError;
@@ -7696,6 +7740,8 @@
             gids = base.gids;
 
             timeStamp = base.timeStamp;
+            firstInstallTime = base.firstInstallTime;
+            lastUpdateTime = base.lastUpdateTime;
             signatures = base.signatures;
             permissionsFixed = base.permissionsFixed;
             haveGids = base.haveGids;
@@ -8565,7 +8611,9 @@
                 serializer.attribute(null, "realName", pkg.realName);
             }
             serializer.attribute(null, "codePath", pkg.codePathString);
-            serializer.attribute(null, "ts", String.valueOf(pkg.timeStamp));
+            serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp));
+            serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime));
+            serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime));
             serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
             if (!pkg.resourcePathString.equals(pkg.codePathString)) {
                 serializer.attribute(null, "resourcePath", pkg.resourcePathString);
@@ -8617,7 +8665,9 @@
             }
             serializer.attribute(null, "flags",
                     Integer.toString(pkg.pkgFlags));
-            serializer.attribute(null, "ts", String.valueOf(pkg.timeStamp));
+            serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp));
+            serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime));
+            serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime));
             serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
             if (pkg.sharedUser == null) {
                 serializer.attribute(null, "userId",
@@ -8973,13 +9023,36 @@
             pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
             PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
                     new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags);
-            String timeStampStr = parser.getAttributeValue(null, "ts");
+            String timeStampStr = parser.getAttributeValue(null, "ft");
             if (timeStampStr != null) {
                 try {
-                    long timeStamp = Long.parseLong(timeStampStr);
+                    long timeStamp = Long.parseLong(timeStampStr, 16);
                     ps.setTimeStamp(timeStamp);
                 } catch (NumberFormatException e) {
                 }
+            } else {
+                timeStampStr = parser.getAttributeValue(null, "ts");
+                if (timeStampStr != null) {
+                    try {
+                        long timeStamp = Long.parseLong(timeStampStr);
+                        ps.setTimeStamp(timeStamp);
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            }
+            timeStampStr = parser.getAttributeValue(null, "it");
+            if (timeStampStr != null) {
+                try {
+                    ps.firstInstallTime = Long.parseLong(timeStampStr, 16);
+                } catch (NumberFormatException e) {
+                }
+            }
+            timeStampStr = parser.getAttributeValue(null, "ut");
+            if (timeStampStr != null) {
+                try {
+                    ps.lastUpdateTime = Long.parseLong(timeStampStr, 16);
+                } catch (NumberFormatException e) {
+                }
             }
             String idStr = parser.getAttributeValue(null, "userId");
             ps.userId = idStr != null ? Integer.parseInt(idStr) : 0;
@@ -9026,6 +9099,8 @@
             String uidError = null;
             int pkgFlags = 0;
             long timeStamp = 0;
+            long firstInstallTime = 0;
+            long lastUpdateTime = 0;
             PackageSettingBase packageSetting = null;
             String version = null;
             int versionCode = 0;
@@ -9065,10 +9140,32 @@
                         pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
                     }
                 }
-                final String timeStampStr = parser.getAttributeValue(null, "ts");
+                String timeStampStr = parser.getAttributeValue(null, "ft");
                 if (timeStampStr != null) {
                     try {
-                        timeStamp = Long.parseLong(timeStampStr);
+                        timeStamp = Long.parseLong(timeStampStr, 16);
+                    } catch (NumberFormatException e) {
+                    }
+                } else {
+                    timeStampStr = parser.getAttributeValue(null, "ts");
+                    if (timeStampStr != null) {
+                        try {
+                            timeStamp = Long.parseLong(timeStampStr);
+                        } catch (NumberFormatException e) {
+                        }
+                    }
+                }
+                timeStampStr = parser.getAttributeValue(null, "it");
+                if (timeStampStr != null) {
+                    try {
+                        firstInstallTime = Long.parseLong(timeStampStr, 16);
+                    } catch (NumberFormatException e) {
+                    }
+                }
+                timeStampStr = parser.getAttributeValue(null, "ut");
+                if (timeStampStr != null) {
+                    try {
+                        lastUpdateTime = Long.parseLong(timeStampStr, 16);
                     } catch (NumberFormatException e) {
                     }
                 }
@@ -9102,6 +9199,8 @@
                                 + parser.getPositionDescription());
                     } else {
                         packageSetting.setTimeStamp(timeStamp);
+                        packageSetting.firstInstallTime = firstInstallTime;
+                        packageSetting.lastUpdateTime = lastUpdateTime;
                     }
                 } else if (sharedIdStr != null) {
                     userId = sharedIdStr != null
@@ -9111,6 +9210,8 @@
                                 new File(codePathStr), new File(resourcePathStr),
                                 nativeLibraryPathStr, userId, versionCode, pkgFlags);
                         packageSetting.setTimeStamp(timeStamp);
+                        packageSetting.firstInstallTime = firstInstallTime;
+                        packageSetting.lastUpdateTime = lastUpdateTime;
                         mPendingPackages.add((PendingPackage) packageSetting);
                         if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name
                                 + ": sharedUserId=" + userId + " pkg="
@@ -9669,7 +9770,7 @@
                doGc = true;
                synchronized (mInstallLock) {
                    final PackageParser.Package pkg =  scanPackageLI(new File(codePath),
-                           parseFlags, 0);
+                           parseFlags, 0, 0);
                    // Scan the package
                    if (pkg != null) {
                        synchronized (mPackages) {
@@ -9894,10 +9995,10 @@
                            synchronized (mPackages) {
                                PackageParser.Package pkg = mPackages.get(mp.packageName);
                                // Recheck for package again.
-                               if (pkg == null ) {
-                                   Slog.w(TAG, " Package " + mp.packageName +
-                                   " doesn't exist. Aborting move");
-                                   returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+                                if (pkg == null) {
+                                    Slog.w(TAG, " Package " + mp.packageName
+                                            + " doesn't exist. Aborting move");
+                                    returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
                                } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) {
                                    Slog.w(TAG, "Package " + mp.packageName + " code path changed from " +
                                            mp.srcArgs.getCodePath() + " to " + pkg.applicationInfo.sourceDir +
@@ -9908,15 +10009,34 @@
                                    final String newCodePath = mp.targetArgs.getCodePath();
                                    final String newResPath = mp.targetArgs.getResourcePath();
                                    final String newNativePath = mp.targetArgs.getNativeLibraryPath();
-                                   pkg.mPath = newCodePath;
-                                   // Move dex files around
-                                   if (moveDexFilesLI(pkg)
-                                           != PackageManager.INSTALL_SUCCEEDED) {
-                                       // Moving of dex files failed. Set
-                                       // error code and abort move.
-                                       pkg.mPath = pkg.mScanPath;
-                                       returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
-                                   } else {
+
+                                    if ((mp.flags & PackageManager.INSTALL_EXTERNAL) == 0) {
+                                        if (mInstaller
+                                                .unlinkNativeLibraryDirectory(pkg.applicationInfo.dataDir) < 0) {
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        } else {
+                                            NativeLibraryHelper.copyNativeBinariesLI(
+                                                    new File(newCodePath), new File(newNativePath));
+                                        }
+                                    } else {
+                                        if (mInstaller.linkNativeLibraryDirectory(
+                                                pkg.applicationInfo.dataDir, newNativePath) < 0) {
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        }
+                                    }
+
+                                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
+                                        pkg.mPath = newCodePath;
+                                        // Move dex files around
+                                        if (moveDexFilesLI(pkg) != PackageManager.INSTALL_SUCCEEDED) {
+                                            // Moving of dex files failed. Set
+                                            // error code and abort move.
+                                            pkg.mPath = pkg.mScanPath;
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        }
+                                    }
+
+                                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
                                        pkg.mScanPath = newCodePath;
                                        pkg.applicationInfo.sourceDir = newCodePath;
                                        pkg.applicationInfo.publicSourceDir = newResPath;
diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
index d9b49fd..e3131fe 100644
--- a/services/java/com/android/server/location/GeocoderProxy.java
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -41,8 +41,8 @@
 
     private final Context mContext;
     private final Intent mIntent;
-    private final Connection mServiceConnection = new Connection();
-    private IGeocodeProvider mProvider;
+    private final Object mMutex = new Object();  // synchronizes access to mServiceConnection
+    private Connection mServiceConnection = new Connection();  // never null
 
     public GeocoderProxy(Context context, String serviceName) {
         mContext = context;
@@ -50,34 +50,48 @@
         mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
+    /**
+     * When unbundled NetworkLocationService package is updated, we
+     * need to unbind from the old version and re-bind to the new one.
+     */
     public void reconnect() {
-        synchronized (mServiceConnection) {
+        synchronized (mMutex) {
             mContext.unbindService(mServiceConnection);
+            mServiceConnection = new Connection();
             mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
         }
     }
 
     private class Connection implements ServiceConnection {
+
+        private IGeocodeProvider mProvider;
+
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "onServiceConnected " + className);
-            synchronized (mServiceConnection) {
+            synchronized (this) {
                 mProvider = IGeocodeProvider.Stub.asInterface(service);
             }
         }
 
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "onServiceDisconnected " + className);
-            synchronized (mServiceConnection) {
+            synchronized (this) {
                 mProvider = null;
             }
         }
+
+        public IGeocodeProvider getProvider() {
+            synchronized (this) {
+                return mProvider;
+            }
+        }
     }
 
     public String getFromLocation(double latitude, double longitude, int maxResults,
             GeocoderParams params, List<Address> addrs) {
         IGeocodeProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
+        synchronized (mMutex) {
+            provider = mServiceConnection.getProvider();
         }
         if (provider != null) {
             try {
@@ -95,8 +109,8 @@
             double upperRightLatitude, double upperRightLongitude, int maxResults,
             GeocoderParams params, List<Address> addrs) {
         IGeocodeProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
+        synchronized (mMutex) {
+            provider = mServiceConnection.getProvider();
         }
         if (provider != null) {
             try {
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index ef2056b..1a1a170 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -45,10 +45,10 @@
 
     private final Context mContext;
     private final String mName;
-    private final String mServiceName;
-    private ILocationProvider mProvider;
-    private Handler mHandler;
-    private final Connection mServiceConnection = new Connection();
+    private final Intent mIntent;
+    private final Handler mHandler;
+    private final Object mMutex = new Object();  // synchronizes access to non-final members
+    private Connection mServiceConnection = new Connection();  // never null
 
     // cached values set by the location manager
     private boolean mLocationTracking = false;
@@ -58,89 +58,105 @@
     private int mNetworkState;
     private NetworkInfo mNetworkInfo;
 
-    // for caching requiresNetwork, requiresSatellite, etc.
-    private DummyLocationProvider mCachedAttributes;
-
     // constructor for proxying location providers implemented in a separate service
     public LocationProviderProxy(Context context, String name, String serviceName,
             Handler handler) {
         mContext = context;
         mName = name;
-        mServiceName = serviceName;
+        mIntent = new Intent(serviceName);
         mHandler = handler;
-        mContext.bindService(new Intent(serviceName), mServiceConnection, Context.BIND_AUTO_CREATE);
+        mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
+    /**
+     * When unbundled NetworkLocationService package is updated, we
+     * need to unbind from the old version and re-bind to the new one.
+     */
     public void reconnect() {
-        synchronized (mServiceConnection) {
-            // unbind first
+        synchronized (mMutex) {
             mContext.unbindService(mServiceConnection);
-            mContext.bindService(new Intent(mServiceName), mServiceConnection,
-                Context.BIND_AUTO_CREATE);
+            mServiceConnection = new Connection();
+            mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
         }
     }
 
-    private class Connection implements ServiceConnection {
+    private class Connection implements ServiceConnection, Runnable {
+
+        private ILocationProvider mProvider;
+
+        // for caching requiresNetwork, requiresSatellite, etc.
+        private DummyLocationProvider mCachedAttributes;  // synchronized by mMutex
+
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className);
-            synchronized (mServiceConnection) {
+            synchronized (this) {
                 mProvider = ILocationProvider.Stub.asInterface(service);
                 if (mProvider != null) {
-                    mHandler.post(mServiceConnectedTask);
+                    mHandler.post(this);
                 }
             }
         }
 
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className);
-            synchronized (mServiceConnection) {
+            synchronized (this) {
                 mProvider = null;
             }
         }
-    }
 
-    private Runnable mServiceConnectedTask = new Runnable() {
+        public synchronized ILocationProvider getProvider() {
+            return mProvider;
+        }
+
+        public synchronized DummyLocationProvider getCachedAttributes() {
+            return mCachedAttributes;
+        }
+
         public void run() {
-            ILocationProvider provider;
-            synchronized (mServiceConnection) {
-                provider = mProvider;
+            synchronized (mMutex) {
+                if (mServiceConnection != this) {
+                    // This ServiceConnection no longer the one we want to bind to.
+                    return;
+                }
+                ILocationProvider provider = getProvider();
                 if (provider == null) {
                     return;
                 }
-            }
 
-            if (mCachedAttributes == null) {
+                // resend previous values from the location manager if the service has restarted
                 try {
-                    mCachedAttributes = new DummyLocationProvider(mName, null);
-                    mCachedAttributes.setRequiresNetwork(provider.requiresNetwork());
-                    mCachedAttributes.setRequiresSatellite(provider.requiresSatellite());
-                    mCachedAttributes.setRequiresCell(provider.requiresCell());
-                    mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost());
-                    mCachedAttributes.setSupportsAltitude(provider.supportsAltitude());
-                    mCachedAttributes.setSupportsSpeed(provider.supportsSpeed());
-                    mCachedAttributes.setSupportsBearing(provider.supportsBearing());
-                    mCachedAttributes.setPowerRequirement(provider.getPowerRequirement());
-                    mCachedAttributes.setAccuracy(provider.getAccuracy());
+                    if (mEnabled) {
+                        provider.enable();
+                    }
+                    if (mLocationTracking) {
+                        provider.enableLocationTracking(true);
+                    }
+                    if (mMinTime >= 0) {
+                        provider.setMinTime(mMinTime, mMinTimeSource);
+                    }
+                    if (mNetworkInfo != null) {
+                        provider.updateNetworkState(mNetworkState, mNetworkInfo);
+                    }
                 } catch (RemoteException e) {
-                    mCachedAttributes = null;
                 }
-            }
 
-            // resend previous values from the location manager if the service has restarted
-            try {
-                if (mEnabled) {
-                    provider.enable();
+                // init cache of parameters
+                if (mCachedAttributes == null) {
+                    try {
+                        mCachedAttributes = new DummyLocationProvider(mName, null);
+                        mCachedAttributes.setRequiresNetwork(provider.requiresNetwork());
+                        mCachedAttributes.setRequiresSatellite(provider.requiresSatellite());
+                        mCachedAttributes.setRequiresCell(provider.requiresCell());
+                        mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost());
+                        mCachedAttributes.setSupportsAltitude(provider.supportsAltitude());
+                        mCachedAttributes.setSupportsSpeed(provider.supportsSpeed());
+                        mCachedAttributes.setSupportsBearing(provider.supportsBearing());
+                        mCachedAttributes.setPowerRequirement(provider.getPowerRequirement());
+                        mCachedAttributes.setAccuracy(provider.getAccuracy());
+                    } catch (RemoteException e) {
+                        mCachedAttributes = null;
+                    }
                 }
-                if (mLocationTracking) {
-                    provider.enableLocationTracking(true);
-                }
-                if (mMinTime >= 0) {
-                    provider.setMinTime(mMinTime, mMinTimeSource);
-                }
-                if (mNetworkInfo != null) {
-                    provider.updateNetworkState(mNetworkState, mNetworkInfo);
-                }
-            } catch (RemoteException e) {
             }
         }
     };
@@ -149,79 +165,101 @@
         return mName;
     }
 
+    private DummyLocationProvider getCachedAttributes() {
+        synchronized (mMutex) {
+            return mServiceConnection.getCachedAttributes();
+        }
+    }
+
     public boolean requiresNetwork() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.requiresNetwork();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.requiresNetwork();
         } else {
             return false;
         }
     }
 
     public boolean requiresSatellite() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.requiresSatellite();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.requiresSatellite();
         } else {
             return false;
         }
     }
 
     public boolean requiresCell() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.requiresCell();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.requiresCell();
         } else {
             return false;
         }
     }
 
     public boolean hasMonetaryCost() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.hasMonetaryCost();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.hasMonetaryCost();
         } else {
             return false;
         }
     }
 
     public boolean supportsAltitude() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.supportsAltitude();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.supportsAltitude();
         } else {
             return false;
         }
     }
 
     public boolean supportsSpeed() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.supportsSpeed();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.supportsSpeed();
         } else {
             return false;
         }
     }
 
      public boolean supportsBearing() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.supportsBearing();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.supportsBearing();
         } else {
             return false;
         }
     }
 
     public int getPowerRequirement() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.getPowerRequirement();
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.getPowerRequirement();
+        } else {
+            return -1;
+        }
+    }
+
+    public int getAccuracy() {
+        DummyLocationProvider cachedAttributes = getCachedAttributes();
+        if (cachedAttributes != null) {
+            return cachedAttributes.getAccuracy();
         } else {
             return -1;
         }
     }
 
     public boolean meetsCriteria(Criteria criteria) {
-       ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                return provider.meetsCriteria(criteria);
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    return provider.meetsCriteria(criteria);
+                } catch (RemoteException e) {
+                }
             }
         }
         // default implementation if we lost connection to the provider
@@ -246,50 +284,42 @@
         return true;
     }
 
-    public int getAccuracy() {
-        if (mCachedAttributes != null) {
-            return mCachedAttributes.getAccuracy();
-        } else {
-            return -1;
-        }
-    }
-
     public void enable() {
-        mEnabled = true;
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.enable();
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            mEnabled = true;
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.enable();
+                } catch (RemoteException e) {
+                }
             }
         }
     }
 
     public void disable() {
-        mEnabled = false;
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.disable();
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            mEnabled = false;
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.disable();
+                } catch (RemoteException e) {
+                }
             }
         }
     }
 
     public boolean isEnabled() {
-        return mEnabled;
+        synchronized (mMutex) {
+            return mEnabled;
+        }
     }
 
     public int getStatus(Bundle extras) {
         ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
+        synchronized (mMutex) {
+            provider = mServiceConnection.getProvider();
         }
         if (provider != null) {
             try {
@@ -301,9 +331,9 @@
     }
 
     public long getStatusUpdateTime() {
-         ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
+        ILocationProvider provider;
+        synchronized (mMutex) {
+            provider = mServiceConnection.getProvider();
         }
         if (provider != null) {
             try {
@@ -315,32 +345,39 @@
      }
 
     public String getInternalState() {
-        try {
-            return mProvider.getInternalState();
-        } catch (RemoteException e) {
-            Log.e(TAG, "getInternalState failed", e);
-            return null;
-        }
-    }
-
-    public boolean isLocationTracking() {
-        return mLocationTracking;
-    }
-
-    public void enableLocationTracking(boolean enable) {
-        mLocationTracking = enable;
-        if (!enable) {
-            mMinTime = -1;
-            mMinTimeSource.clear();
-        }
         ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
+        synchronized (mMutex) {
+            provider = mServiceConnection.getProvider();
         }
         if (provider != null) {
             try {
-                provider.enableLocationTracking(enable);
+                return provider.getInternalState();
             } catch (RemoteException e) {
+                Log.e(TAG, "getInternalState failed", e);
+            }
+        }
+        return null;
+    }
+
+    public boolean isLocationTracking() {
+        synchronized (mMutex) {
+            return mLocationTracking;
+        }
+    }
+
+    public void enableLocationTracking(boolean enable) {
+        synchronized (mMutex) {
+            mLocationTracking = enable;
+            if (!enable) {
+                mMinTime = -1;
+                mMinTimeSource.clear();
+            }
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.enableLocationTracking(enable);
+                } catch (RemoteException e) {
+                }
             }
         }
     }
@@ -350,88 +387,84 @@
     }
 
     public long getMinTime() {
-        return mMinTime;
+        synchronized (mMutex) {
+            return mMinTime;
+        }
     }
 
     public void setMinTime(long minTime, WorkSource ws) {
-        mMinTime = minTime;
-        mMinTimeSource.set(ws);
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.setMinTime(minTime, ws);
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            mMinTime = minTime;
+            mMinTimeSource.set(ws);
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.setMinTime(minTime, ws);
+                } catch (RemoteException e) {
+                }
             }
         }
     }
 
     public void updateNetworkState(int state, NetworkInfo info) {
-        mNetworkState = state;
-        mNetworkInfo = info;
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.updateNetworkState(state, info);
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            mNetworkState = state;
+            mNetworkInfo = info;
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.updateNetworkState(state, info);
+                } catch (RemoteException e) {
+                }
             }
         }
     }
 
     public void updateLocation(Location location) {
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.updateLocation(location);
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.updateLocation(location);
+                } catch (RemoteException e) {
+                }
             }
         }
     }
 
     public boolean sendExtraCommand(String command, Bundle extras) {
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.sendExtraCommand(command, extras);
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    return provider.sendExtraCommand(command, extras);
+                } catch (RemoteException e) {
+                }
             }
         }
         return false;
     }
 
     public void addListener(int uid) {
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.addListener(uid);
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.addListener(uid);
+                } catch (RemoteException e) {
+                }
             }
         }
     }
 
     public void removeListener(int uid) {
-        ILocationProvider provider;
-        synchronized (mServiceConnection) {
-            provider = mProvider;
-        }
-        if (provider != null) {
-            try {
-                provider.removeListener(uid);
-            } catch (RemoteException e) {
+        synchronized (mMutex) {
+            ILocationProvider provider = mServiceConnection.getProvider();
+            if (provider != null) {
+                try {
+                    provider.removeListener(uid);
+                } catch (RemoteException e) {
+                }
             }
         }
     }
diff --git a/telephony/mockril/Android.mk b/telephony/mockril/Android.mk
new file mode 100644
index 0000000..7c39cb1
--- /dev/null
+++ b/telephony/mockril/Android.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+LOCAL_PATH:=$(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := core framework
+
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := mockrilcontroller
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
new file mode 100644
index 0000000..9b6a850
--- /dev/null
+++ b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.mockril;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.telephony.PhoneNumberUtils;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+import com.android.internal.telephony.ril_proto.RilCmds;
+import com.google.protobuf.micro.MessageMicro;
+
+import java.io.IOException;
+
+/**
+ * Contain a list of commands to control Mock RIL. Before using these commands the devices
+ * needs to be set with Mock RIL. Refer to hardware/ril/mockril/README.txt for details.
+ *
+ */
+public class MockRilController {
+    private static final String TAG = "MockRILController";
+    private RilChannel mRilChannel = null;
+    private Msg mMessage = null;
+
+    public MockRilController() throws IOException {
+        mRilChannel = RilChannel.makeRilChannel();
+    }
+
+    /**
+     * Close the channel after the communication is done.
+     * This method has to be called after the test is finished.
+     */
+    public void closeChannel() {
+        mRilChannel.close();
+    }
+
+    /**
+     * Send commands and return true on success
+     * @param cmd for MsgHeader
+     * @param token for MsgHeader
+     * @param status for MsgHeader
+     * @param pbData for Msg data
+     * @return true if command is sent successfully, false if it fails
+     */
+    private boolean sendCtrlCommand(int cmd, long token, int status, MessageMicro pbData) {
+        try {
+            Msg.send(mRilChannel, cmd, token, status, pbData);
+        } catch (IOException e) {
+            Log.v(TAG, "send command : %d failed: " + e.getStackTrace());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Get control response
+     * @return Msg if response is received, else return null.
+     */
+    private Msg getCtrlResponse() {
+        Msg response = null;
+        try {
+            response = Msg.recv(mRilChannel);
+        } catch (IOException e) {
+            Log.v(TAG, "receive response for getRadioState() error: " + e.getStackTrace());
+            return null;
+        }
+        return response;
+    }
+
+    /**
+     * @return the radio state if it is valid, otherwise return -1
+     */
+    public int getRadioState() {
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_GET_RADIO_STATE, 0, 0, null)) {
+            return -1;
+        }
+        Msg response = getCtrlResponse();
+        if (response == null) {
+            Log.v(TAG, "failed to get response");
+            return -1;
+        }
+        response.printHeader(TAG);
+        RilCtrlCmds.CtrlRspRadioState resp =
+            response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+        int state = resp.getState();
+        if ((state >= RilCmds.RADIOSTATE_OFF) && (state <= RilCmds.RADIOSTATE_NV_READY))
+            return state;
+        else
+            return -1;
+    }
+
+    /**
+     * Set the radio state of mock ril to the given state
+     * @param state for given radio state
+     * @return true if the state is set successful, false if it fails
+     */
+    public boolean setRadioState(int state) {
+        RilCtrlCmds.CtrlReqRadioState req = new RilCtrlCmds.CtrlReqRadioState();
+        if (state < 0 || state > RilCmds.RADIOSTATE_NV_READY) {
+            Log.v(TAG, "the give radio state is not valid.");
+            return false;
+        }
+        req.setState(state);
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, req)) {
+            Log.v(TAG, "send set radio state request failed.");
+            return false;
+        }
+        Msg response = getCtrlResponse();
+        if (response == null) {
+            Log.v(TAG, "failed to get response for setRadioState");
+            return false;
+        }
+        response.printHeader(TAG);
+        RilCtrlCmds.CtrlRspRadioState resp =
+            response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+        int curstate = resp.getState();
+        return curstate == state;
+    }
+
+
+
+    /**
+     * Set an MT call
+     *
+     * @param phoneNumber for the number shown
+     */
+    public boolean setMTCall(String phoneNumber) {
+        RilCtrlCmds.CtrlReqSetMTCall req = new RilCtrlCmds.CtrlReqSetMTCall();
+
+        // Check whether it is a valid number
+        req.setPhoneNumber(phoneNumber);
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_MT_CALL, 0, 0, req)) {
+            Log.v(TAG, "send CMD_SET_MT_CALL request failed");
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/telephony/tests/telephonytests/Android.mk b/telephony/tests/telephonytests/Android.mk
index 45e265a..98e4403 100644
--- a/telephony/tests/telephonytests/Android.mk
+++ b/telephony/tests/telephonytests/Android.mk
@@ -5,6 +5,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_PACKAGE_NAME := FrameworksTelephonyTests
diff --git a/telephony/tests/telephonytests/AndroidManifest.xml b/telephony/tests/telephonytests/AndroidManifest.xml
index 6a97423..ba1d957 100644
--- a/telephony/tests/telephonytests/AndroidManifest.xml
+++ b/telephony/tests/telephonytests/AndroidManifest.xml
@@ -32,6 +32,13 @@
         android:targetPackage="com.android.frameworks.telephonytests"
         android:label="Frameworks Telephony Tests">
     </instrumentation>
+
+    <instrumentation android:name=".TelephonyMockRilTestRunner"
+        android:targetPackage="com.android.frameworks.telephonytests"
+        android:label="Test Runner for Mock Ril Tests"
+    />
+
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
 
 </manifest>
diff --git a/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
new file mode 100644
index 0000000..9192f57
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.telephonytests;
+
+import android.os.Bundle;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import java.io.IOException;
+
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.mockril.MockRilTest;
+
+import junit.framework.TestSuite;
+
+public class TelephonyMockRilTestRunner extends InstrumentationTestRunner {
+
+    public RilChannel mMockRilChannel;
+
+    @Override
+    public TestSuite getAllTests() {
+        log("getAllTests E");
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(MockRilTest.class);
+        log("getAllTests X");
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        log("getLoader EX");
+        return TelephonyMockRilTestRunner.class.getClassLoader();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        log("onCreate E");
+        try {
+            mMockRilChannel = RilChannel.makeRilChannel();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        log("onCreate X");
+
+        super.onCreate(icicle);
+    }
+
+    @Override
+    public void onDestroy() {
+        // I've not seen this called
+        log("onDestroy EX");
+        super.onDestroy();
+    }
+
+    @Override
+    public void onStart() {
+        // Called when the instrumentation thread is started.
+        // At the moment we don't need the thread so return
+        // which will shut down this unused thread.
+        log("onStart EX");
+        super.onStart();
+    }
+
+    @Override
+    public void finish(int resultCode, Bundle results) {
+        // Called when complete so I ask the mMockRilChannel to quit.
+        log("finish E");
+        mMockRilChannel.close();
+        log("finish X");
+        super.finish(resultCode, results);
+    }
+
+    private void log(String s) {
+        Log.e("TelephonyMockRilTestRunner", s);
+    }
+}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
new file mode 100644
index 0000000..f0d5b31
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.mockril;
+
+import android.util.Log;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+import com.android.internal.telephony.ril_proto.RilCmds;
+
+import com.android.frameworks.telephonytests.TelephonyMockRilTestRunner;
+import com.google.protobuf.micro.InvalidProtocolBufferMicroException;
+
+// Test suite for test ril
+public class MockRilTest extends InstrumentationTestCase {
+    private static final String TAG = "MockRilTest";
+
+    RilChannel mMockRilChannel;
+    TelephonyMockRilTestRunner mRunner;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRunner = (TelephonyMockRilTestRunner)getInstrumentation();
+        mMockRilChannel = mRunner.mMockRilChannel;
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    static void log(String s) {
+        Log.v(TAG, s);
+    }
+
+    /**
+     * Test protobuf serialization and deserialization
+     * @throws InvalidProtocolBufferMicroException
+     */
+    public void testProtobufSerDes() throws InvalidProtocolBufferMicroException {
+        log("testProtobufSerdes E");
+
+        RilCtrlCmds.CtrlRspRadioState rs = new RilCtrlCmds.CtrlRspRadioState();
+        assertTrue(String.format("expected rs.state == 0 was %d", rs.getState()),
+                rs.getState() == 0);
+        rs.setState(1);
+        assertTrue(String.format("expected rs.state == 1 was %d", rs.getState()),
+                rs.getState() == 1);
+
+        byte[] rs_ser = rs.toByteArray();
+        RilCtrlCmds.CtrlRspRadioState rsNew = RilCtrlCmds.CtrlRspRadioState.parseFrom(rs_ser);
+        assertTrue(String.format("expected rsNew.state == 1 was %d", rs.getState()),
+                rs.getState() == 1);
+
+        log("testProtobufSerdes X");
+    }
+
+    /**
+     * Test echo command works using writeMsg & readMsg
+     */
+    public void testEchoMsg() throws IOException {
+        log("testEchoMsg E");
+
+        MsgHeader mh = new MsgHeader();
+        mh.setCmd(0);
+        mh.setToken(1);
+        mh.setStatus(2);
+        ByteBuffer data = ByteBuffer.allocate(3);
+        data.put((byte)3);
+        data.put((byte)4);
+        data.put((byte)5);
+        Msg.send(mMockRilChannel, mh, data);
+
+        Msg respMsg = Msg.recv(mMockRilChannel);
+        assertTrue(String.format("expected mhd.header.cmd == 0 was %d",respMsg.getCmd()),
+                respMsg.getCmd() == 0);
+        assertTrue(String.format("expected mhd.header.token == 1 was %d",respMsg.getToken()),
+                respMsg.getToken() == 1);
+        assertTrue(String.format("expected mhd.header.status == 2 was %d", respMsg.getStatus()),
+                respMsg.getStatus() == 2);
+        assertTrue(String.format("expected mhd.data[0] == 3 was %d", respMsg.getData(0)),
+                respMsg.getData(0) == 3);
+        assertTrue(String.format("expected mhd.data[1] == 4 was %d", respMsg.getData(1)),
+                respMsg.getData(1) == 4);
+        assertTrue(String.format("expected mhd.data[2] == 5 was %d", respMsg.getData(2)),
+                respMsg.getData(2) == 5);
+
+        log("testEchoMsg X");
+    }
+
+    /**
+     * Test get as
+     */
+    public void testGetAs() {
+        log("testGetAs E");
+
+        // Use a message header as the protobuf data content
+        MsgHeader mh = new MsgHeader();
+        mh.setCmd(12345);
+        mh.setToken(9876);
+        mh.setStatus(7654);
+        mh.setLengthData(4321);
+        byte[] data = mh.toByteArray();
+        MsgHeader mhResult = Msg.getAs(MsgHeader.class, data);
+
+        assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+                mhResult.getCmd() == 12345);
+        assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+                mhResult.getToken() == 9876);
+        assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+                mhResult.getStatus() == 7654);
+        assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+                mhResult.getLengthData() == 4321);
+
+        Msg msg = Msg.obtain();
+        msg.setData(ByteBuffer.wrap(data));
+
+        mhResult = msg.getDataAs(MsgHeader.class);
+
+        assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+                mhResult.getCmd() == 12345);
+        assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+                mhResult.getToken() == 9876);
+        assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+                mhResult.getStatus() == 7654);
+        assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+                mhResult.getLengthData() == 4321);
+
+        log("testGetAs X");
+    }
+
+    public void testGetRadioState() throws IOException {
+        log("testGetRadioState E");
+
+        Msg.send(mMockRilChannel, 1, 9876, 0, null);
+
+        Msg resp = Msg.recv(mMockRilChannel);
+        //resp.printHeader("testGetRadioState");
+
+        assertTrue(String.format("expected cmd == 1 was %d", resp.getCmd()),
+                resp.getCmd() == 1);
+        assertTrue(String.format("expected token == 9876 was %d", resp.getToken()),
+                resp.getToken() == 9876);
+        assertTrue(String.format("expected status == 0 was %d", resp.getStatus()),
+                resp.getStatus() == 0);
+
+        RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+        int state = rsp.getState();
+        log("testGetRadioState state=" + state);
+        assertTrue(String.format("expected RadioState >= 0 && RadioState <= 9 was %d", state),
+                ((state >= 0) && (state <= 9)));
+
+        log("testGetRadioState X");
+    }
+
+    public void testSetRadioState() throws IOException {
+        log("testSetRadioState E");
+
+        RilCtrlCmds.CtrlReqRadioState cmdrs = new RilCtrlCmds.CtrlReqRadioState();
+        assertEquals(0, cmdrs.getState());
+
+        cmdrs.setState(RilCmds.RADIOSTATE_SIM_NOT_READY);
+        assertEquals(2, cmdrs.getState());
+
+        Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, cmdrs);
+
+        Msg resp = Msg.recv(mMockRilChannel);
+
+        RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+        int state = rsp.getState();
+        log("get response for testSetRadioState: " + state);
+        assertTrue(RilCmds.RADIOSTATE_SIM_NOT_READY == state);
+    }
+}
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index c23da20..f55bade 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -43,9 +43,11 @@
 import java.util.Map;
 
 /**
- * Class that handles an audio call over SIP.
+ * Class that handles an Internet audio call over SIP. {@link SipManager}
+ * facilitates instantiating a {@code SipAudioCall} object for making/receiving
+ * calls. See {@link SipManager#makeAudioCall} and
+ * {@link SipManager#takeAudioCall}.
  */
-/** @hide */
 public class SipAudioCall {
     private static final String TAG = SipAudioCall.class.getSimpleName();
     private static final boolean RELEASE_SOCKET = true;
@@ -56,7 +58,7 @@
     public static class Listener {
         /**
          * Called when the call object is ready to make another call.
-         * The default implementation calls {@link #onChange}.
+         * The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that is ready to make another call
          */
@@ -66,7 +68,7 @@
 
         /**
          * Called when a request is sent out to initiate a new call.
-         * The default implementation calls {@link #onChange}.
+         * The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that carries out the audio call
          */
@@ -76,7 +78,7 @@
 
         /**
          * Called when a new call comes in.
-         * The default implementation calls {@link #onChange}.
+         * The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that carries out the audio call
          * @param caller the SIP profile of the caller
@@ -87,7 +89,7 @@
 
         /**
          * Called when a RINGING response is received for the INVITE request
-         * sent. The default implementation calls {@link #onChange}.
+         * sent. The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that carries out the audio call
          */
@@ -97,7 +99,7 @@
 
         /**
          * Called when the session is established.
-         * The default implementation calls {@link #onChange}.
+         * The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that carries out the audio call
          */
@@ -107,7 +109,7 @@
 
         /**
          * Called when the session is terminated.
-         * The default implementation calls {@link #onChange}.
+         * The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that carries out the audio call
          */
@@ -117,7 +119,7 @@
 
         /**
          * Called when the peer is busy during session initialization.
-         * The default implementation calls {@link #onChange}.
+         * The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that carries out the audio call
          */
@@ -127,7 +129,7 @@
 
         /**
          * Called when the call is on hold.
-         * The default implementation calls {@link #onChange}.
+         * The default implementation calls {@link #onChanged}.
          *
          * @param call the call object that carries out the audio call
          */
@@ -257,8 +259,10 @@
      *
      * @return true if the call is established
      */
-    public synchronized boolean isInCall() {
-        return mInCall;
+    public boolean isInCall() {
+        synchronized (this) {
+            return mInCall;
+        }
     }
 
     /**
@@ -266,8 +270,10 @@
      *
      * @return true if the call is on hold
      */
-    public synchronized boolean isOnHold() {
-        return mHold;
+    public boolean isOnHold() {
+        synchronized (this) {
+            return mHold;
+        }
     }
 
     /**
@@ -299,8 +305,10 @@
      *
      * @return the local SIP profile
      */
-    public synchronized SipProfile getLocalProfile() {
-        return mLocalProfile;
+    public SipProfile getLocalProfile() {
+        synchronized (this) {
+            return mLocalProfile;
+        }
     }
 
     /**
@@ -308,8 +316,10 @@
      *
      * @return the peer's SIP profile
      */
-    public synchronized SipProfile getPeerProfile() {
-        return (mSipSession == null) ? null : mSipSession.getPeerProfile();
+    public SipProfile getPeerProfile() {
+        synchronized (this) {
+            return (mSipSession == null) ? null : mSipSession.getPeerProfile();
+        }
     }
 
     /**
@@ -318,9 +328,11 @@
      *
      * @return the session state
      */
-    public synchronized int getState() {
-        if (mSipSession == null) return SipSession.State.READY_TO_CALL;
-        return mSipSession.getState();
+    public int getState() {
+        synchronized (this) {
+            if (mSipSession == null) return SipSession.State.READY_TO_CALL;
+            return mSipSession.getState();
+        }
     }
 
 
@@ -330,8 +342,10 @@
      * @return the session object that carries this call
      * @hide
      */
-    public synchronized SipSession getSipSession() {
-        return mSipSession;
+    public SipSession getSipSession() {
+        synchronized (this) {
+            return mSipSession;
+        }
     }
 
     private SipSession.Listener createListener() {
@@ -364,22 +378,25 @@
             }
 
             @Override
-            public synchronized void onRinging(SipSession session,
+            public void onRinging(SipSession session,
                     SipProfile peerProfile, String sessionDescription) {
-                if ((mSipSession == null) || !mInCall
-                        || !session.getCallId().equals(mSipSession.getCallId())) {
-                    // should not happen
-                    session.endCall();
-                    return;
-                }
+                synchronized (SipAudioCall.this) {
+                    if ((mSipSession == null) || !mInCall
+                            || !session.getCallId().equals(
+                                    mSipSession.getCallId())) {
+                        // should not happen
+                        session.endCall();
+                        return;
+                    }
 
-                // session changing request
-                try {
-                    String answer = createAnswer(sessionDescription).encode();
-                    mSipSession.answerCall(answer, SESSION_TIMEOUT);
-                } catch (Throwable e) {
-                    Log.e(TAG, "onRinging()", e);
-                    session.endCall();
+                    // session changing request
+                    try {
+                        String answer = createAnswer(sessionDescription).encode();
+                        mSipSession.answerCall(answer, SESSION_TIMEOUT);
+                    } catch (Throwable e) {
+                        Log.e(TAG, "onRinging()", e);
+                        session.endCall();
+                    }
                 }
             }
 
@@ -508,18 +525,22 @@
      * @throws SipException if the SIP service fails to attach this object to
      *        the session
      */
-    public synchronized void attachCall(SipSession session,
-            String sessionDescription) throws SipException {
-        mSipSession = session;
-        mPeerSd = sessionDescription;
-        Log.v(TAG, "attachCall()" + mPeerSd);
-        try {
-            session.setListener(createListener());
+    public void attachCall(SipSession session, String sessionDescription)
+            throws SipException {
+        synchronized (this) {
+            mSipSession = session;
+            mPeerSd = sessionDescription;
+            Log.v(TAG, "attachCall()" + mPeerSd);
+            try {
+                session.setListener(createListener());
 
-            if (getState() == SipSession.State.INCOMING_CALL) startRinging();
-        } catch (Throwable e) {
-            Log.e(TAG, "attachCall()", e);
-            throwSipException(e);
+                if (getState() == SipSession.State.INCOMING_CALL) {
+                    startRinging();
+                }
+            } catch (Throwable e) {
+                Log.e(TAG, "attachCall()", e);
+                throwSipException(e);
+            }
         }
     }
 
@@ -529,7 +550,7 @@
      * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
-     * @param callee the SIP profile to make the call to
+     * @param peerProfile the SIP profile to make the call to
      * @param sipSession the {@link SipSession} for carrying out the call
      * @param timeout the timeout value in seconds. Default value (defined by
      *        SIP protocol) is used if {@code timeout} is zero or negative.
@@ -537,15 +558,19 @@
      * @throws SipException if the SIP service fails to create a session for the
      *        call
      */
-    public synchronized void makeCall(SipProfile peerProfile,
-            SipSession sipSession, int timeout) throws SipException {
-        mSipSession = sipSession;
-        try {
-            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
-            sipSession.setListener(createListener());
-            sipSession.makeCall(peerProfile, createOffer().encode(), timeout);
-        } catch (IOException e) {
-            throw new SipException("makeCall()", e);
+    public void makeCall(SipProfile peerProfile, SipSession sipSession,
+            int timeout) throws SipException {
+        synchronized (this) {
+            mSipSession = sipSession;
+            try {
+                mAudioStream = new AudioStream(InetAddress.getByName(
+                        getLocalIp()));
+                sipSession.setListener(createListener());
+                sipSession.makeCall(peerProfile, createOffer().encode(),
+                        timeout);
+            } catch (IOException e) {
+                throw new SipException("makeCall()", e);
+            }
         }
     }
 
@@ -553,13 +578,15 @@
      * Ends a call.
      * @throws SipException if the SIP service fails to end the call
      */
-    public synchronized void endCall() throws SipException {
-        stopRinging();
-        stopCall(RELEASE_SOCKET);
-        mInCall = false;
+    public void endCall() throws SipException {
+        synchronized (this) {
+            stopRinging();
+            stopCall(RELEASE_SOCKET);
+            mInCall = false;
 
-        // perform the above local ops first and then network op
-        if (mSipSession != null) mSipSession.endCall();
+            // perform the above local ops first and then network op
+            if (mSipSession != null) mSipSession.endCall();
+        }
     }
 
     /**
@@ -574,13 +601,15 @@
      * @see Listener.onError
      * @throws SipException if the SIP service fails to hold the call
      */
-    public synchronized void holdCall(int timeout) throws SipException {
+    public void holdCall(int timeout) throws SipException {
+        synchronized (this) {
         if (mHold) return;
-        mSipSession.changeCall(createHoldOffer().encode(), timeout);
-        mHold = true;
+            mSipSession.changeCall(createHoldOffer().encode(), timeout);
+            mHold = true;
 
-        AudioGroup audioGroup = getAudioGroup();
-        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+            AudioGroup audioGroup = getAudioGroup();
+            if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+        }
     }
 
     /**
@@ -594,13 +623,16 @@
      * @see Listener.onError
      * @throws SipException if the SIP service fails to answer the call
      */
-    public synchronized void answerCall(int timeout) throws SipException {
-        stopRinging();
-        try {
-            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
-            mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
-        } catch (IOException e) {
-            throw new SipException("answerCall()", e);
+    public void answerCall(int timeout) throws SipException {
+        synchronized (this) {
+            stopRinging();
+            try {
+                mAudioStream = new AudioStream(InetAddress.getByName(
+                        getLocalIp()));
+                mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
+            } catch (IOException e) {
+                throw new SipException("answerCall()", e);
+            }
         }
     }
 
@@ -616,12 +648,14 @@
      * @see Listener.onError
      * @throws SipException if the SIP service fails to unhold the call
      */
-    public synchronized void continueCall(int timeout) throws SipException {
-        if (!mHold) return;
-        mSipSession.changeCall(createContinueOffer().encode(), timeout);
-        mHold = false;
-        AudioGroup audioGroup = getAudioGroup();
-        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
+    public void continueCall(int timeout) throws SipException {
+        synchronized (this) {
+            if (!mHold) return;
+            mSipSession.changeCall(createContinueOffer().encode(), timeout);
+            mHold = false;
+            AudioGroup audioGroup = getAudioGroup();
+            if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
+        }
     }
 
     private SimpleSessionDescription createOffer() {
@@ -739,12 +773,15 @@
     }
 
     /** Toggles mute. */
-    public synchronized void toggleMute() {
-        AudioGroup audioGroup = getAudioGroup();
-        if (audioGroup != null) {
-            audioGroup.setMode(
-                    mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED);
-            mMuted = !mMuted;
+    public void toggleMute() {
+        synchronized (this) {
+            AudioGroup audioGroup = getAudioGroup();
+            if (audioGroup != null) {
+                audioGroup.setMode(mMuted
+                        ? AudioGroup.MODE_NORMAL
+                        : AudioGroup.MODE_MUTED);
+                mMuted = !mMuted;
+            }
         }
     }
 
@@ -753,14 +790,18 @@
      *
      * @return true if the call is muted
      */
-    public synchronized boolean isMuted() {
-        return mMuted;
+    public boolean isMuted() {
+        synchronized (this) {
+            return mMuted;
+        }
     }
 
     /** Puts the device to speaker mode. */
-    public synchronized void setSpeakerMode(boolean speakerMode) {
-        ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
-                .setSpeakerphoneOn(speakerMode);
+    public void setSpeakerMode(boolean speakerMode) {
+        synchronized (this) {
+            ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
+                    .setSpeakerphoneOn(speakerMode);
+        }
     }
 
     /**
@@ -785,14 +826,16 @@
      *        inputs.
      * @param result the result message to send when done
      */
-    public synchronized void sendDtmf(int code, Message result) {
-        AudioGroup audioGroup = getAudioGroup();
-        if ((audioGroup != null) && (mSipSession != null)
-                && (SipSession.State.IN_CALL == getState())) {
-            Log.v(TAG, "send DTMF: " + code);
-            audioGroup.sendDtmf(code);
+    public void sendDtmf(int code, Message result) {
+        synchronized (this) {
+            AudioGroup audioGroup = getAudioGroup();
+            if ((audioGroup != null) && (mSipSession != null)
+                    && (SipSession.State.IN_CALL == getState())) {
+                Log.v(TAG, "send DTMF: " + code);
+                audioGroup.sendDtmf(code);
+            }
+            if (result != null) result.sendToTarget();
         }
-        if (result != null) result.sendToTarget();
     }
 
     /**
@@ -806,8 +849,10 @@
      *      yet been set up
      * @hide
      */
-    public synchronized AudioStream getAudioStream() {
-        return mAudioStream;
+    public AudioStream getAudioStream() {
+        synchronized (this) {
+            return mAudioStream;
+        }
     }
 
     /**
@@ -824,9 +869,11 @@
      * @see #getAudioStream
      * @hide
      */
-    public synchronized AudioGroup getAudioGroup() {
-        if (mAudioGroup != null) return mAudioGroup;
-        return ((mAudioStream == null) ? null : mAudioStream.getGroup());
+    public AudioGroup getAudioGroup() {
+        synchronized (this) {
+            if (mAudioGroup != null) return mAudioGroup;
+            return ((mAudioStream == null) ? null : mAudioStream.getGroup());
+        }
     }
 
     /**
@@ -837,11 +884,13 @@
      * @see #getAudioStream
      * @hide
      */
-    public synchronized void setAudioGroup(AudioGroup group) {
-        if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
-            mAudioStream.join(group);
+    public void setAudioGroup(AudioGroup group) {
+        synchronized (this) {
+            if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
+                mAudioStream.join(group);
+            }
+            mAudioGroup = group;
         }
-        mAudioGroup = group;
     }
 
     /**
@@ -981,8 +1030,10 @@
      *
      * @param enabled true to enable; false to disable
      */
-    public synchronized void setRingbackToneEnabled(boolean enabled) {
-        mRingbackToneEnabled = enabled;
+    public void setRingbackToneEnabled(boolean enabled) {
+        synchronized (this) {
+            mRingbackToneEnabled = enabled;
+        }
     }
 
     /**
@@ -990,8 +1041,10 @@
      *
      * @param enabled true to enable; false to disable
      */
-    public synchronized void setRingtoneEnabled(boolean enabled) {
-        mRingtoneEnabled = enabled;
+    public void setRingtoneEnabled(boolean enabled) {
+        synchronized (this) {
+            mRingtoneEnabled = enabled;
+        }
     }
 
     private void startRingbackTone() {
diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java
index a55ab25..6aee5f1 100644
--- a/voip/java/android/net/sip/SipErrorCode.java
+++ b/voip/java/android/net/sip/SipErrorCode.java
@@ -19,10 +19,9 @@
 /**
  * Defines error code returned in
  * {@link SipRegistrationListener#onRegistrationFailed},
- * {@link ISipSessionListener#onError},
- * {@link ISipSessionListener#onCallChangeFailed} and
- * {@link ISipSessionListener#onRegistrationFailed}.
- * @hide
+ * {@link SipSession.Listener#onError},
+ * {@link SipSession.Listener#onCallChangeFailed} and
+ * {@link SipSession.Listener#onRegistrationFailed}.
  */
 public class SipErrorCode {
     /** Not an error. */
diff --git a/voip/java/android/net/sip/SipException.java b/voip/java/android/net/sip/SipException.java
index f0d846b..225b94f 100644
--- a/voip/java/android/net/sip/SipException.java
+++ b/voip/java/android/net/sip/SipException.java
@@ -18,7 +18,6 @@
 
 /**
  * General SIP-related exception class.
- * @hide
  */
 public class SipException extends Exception {
     public SipException() {
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 8c32aa0..ee0e3cd 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -48,7 +48,8 @@
  * <li>process SIP events directly with a {@link SipSession} created by
  *      {@link #createSipSession}.</li>
  * </ul>
- * @hide
+ * {@code SipManager} can only be instantiated if SIP API is supported by the
+ * device. (See {@link #isApiSupported}).
  */
 public class SipManager {
     /**
@@ -58,10 +59,17 @@
      */
     public static final int INCOMING_CALL_RESULT_CODE = 101;
 
-    /** Part of the incoming call intent. */
+    /**
+     * Key to retrieve the call ID from an incoming call intent.
+     * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
+     */
     public static final String EXTRA_CALL_ID = "android:sipCallID";
 
-    /** Part of the incoming call intent. */
+    /**
+     * Key to retrieve the offered session description from an incoming call
+     * intent.
+     * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
+     */
     public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
 
     /**
@@ -178,7 +186,11 @@
      * make subsequent calls through {@link #makeAudioCall}. If the
      * auto-registration option is enabled in the profile, the SIP service
      * will register the profile to the corresponding SIP provider periodically
-     * in order to receive calls from the provider.
+     * in order to receive calls from the provider. When the SIP service
+     * receives a new call, it will send out an intent with the provided action
+     * string. The intent contains a call ID extra and an offer session
+     * description string extra. Use {@link #getCallId} and
+     * {@link #getOfferSessionDescription} to retrieve those extras.
      *
      * @param localProfile the SIP profile to receive incoming calls for
      * @param incomingCallPendingIntent When an incoming call is received, the
@@ -194,6 +206,9 @@
      * @throws NullPointerException if {@code incomingCallPendingIntent} is null
      * @throws SipException if the profile contains incorrect settings or
      *      calling the SIP service results in an error
+     * @see #isIncomingCallIntent
+     * @see #getCallId
+     * @see #getOfferSessionDescription
      */
     public void open(SipProfile localProfile,
             PendingIntent incomingCallPendingIntent,
@@ -291,7 +306,8 @@
      * @param peerProfile the SIP profile to make the call to
      * @param listener to listen to the call events from {@link SipAudioCall};
      *      can be null
-     * @param timeout the timeout value in seconds
+     * @param timeout the timeout value in seconds. Default value (defined by
+     *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @return a {@link SipAudioCall} object
      * @throws SipException if calling the SIP service results in an error
      * @see SipAudioCall.Listener.onError
@@ -321,7 +337,8 @@
      * @param peerProfileUri URI of the SIP profile to make the call to
      * @param listener to listen to the call events from {@link SipAudioCall};
      *      can be null
-     * @param timeout the timeout value in seconds
+     * @param timeout the timeout value in seconds. Default value (defined by
+     *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @return a {@link SipAudioCall} object
      * @throws SipException if calling the SIP service results in an error
      * @see SipAudioCall.Listener.onError
@@ -489,7 +506,7 @@
     }
 
     /**
-     * Gets the {@link ISipSession} that handles the incoming call. For audio
+     * Gets the {@link SipSession} that handles the incoming call. For audio
      * calls, consider to use {@link SipAudioCall} to handle the incoming call.
      * See {@link #takeAudioCall}. Note that the method may be called only once
      * for the same intent. For subsequent calls on the same intent, the method
@@ -498,11 +515,12 @@
      * @param incomingCallIntent the incoming call broadcast intent
      * @return the session object that handles the incoming call
      */
-    public ISipSession getSessionFor(Intent incomingCallIntent)
+    public SipSession getSessionFor(Intent incomingCallIntent)
             throws SipException {
         try {
             String callId = getCallId(incomingCallIntent);
-            return mSipService.getPendingSession(callId);
+            ISipSession s = mSipService.getPendingSession(callId);
+            return new SipSession(s);
         } catch (RemoteException e) {
             throw new SipException("getSessionFor()", e);
         }
@@ -514,8 +532,8 @@
     }
 
     /**
-     * Creates a {@link ISipSession} with the specified profile. Use other
-     * methods, if applicable, instead of interacting with {@link ISipSession}
+     * Creates a {@link SipSession} with the specified profile. Use other
+     * methods, if applicable, instead of interacting with {@link SipSession}
      * directly.
      *
      * @param localProfile the SIP profile the session is associated with
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
index 6d5cb3c..dddb07d 100644
--- a/voip/java/android/net/sip/SipProfile.java
+++ b/voip/java/android/net/sip/SipProfile.java
@@ -33,7 +33,6 @@
 
 /**
  * Class containing a SIP account, domain and server information.
- * @hide
  */
 public class SipProfile implements Parcelable, Serializable, Cloneable {
     private static final long serialVersionUID = 1L;
diff --git a/voip/java/android/net/sip/SipRegistrationListener.java b/voip/java/android/net/sip/SipRegistrationListener.java
index 37c9ce2..e1f35ad 100644
--- a/voip/java/android/net/sip/SipRegistrationListener.java
+++ b/voip/java/android/net/sip/SipRegistrationListener.java
@@ -18,7 +18,6 @@
 
 /**
  * Listener class to listen to SIP registration events.
- * @hide
  */
 public interface SipRegistrationListener {
     /**
diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java
index 0cc7206..9c08e46 100644
--- a/voip/java/android/net/sip/SipSession.java
+++ b/voip/java/android/net/sip/SipSession.java
@@ -22,14 +22,12 @@
 /**
  * A SIP session that is associated with a SIP dialog or a standalone
  * transaction not within a dialog.
- * @hide
  */
 public final class SipSession {
     private static final String TAG = "SipSession";
 
     /**
      * Defines {@link SipSession} states.
-     * @hide
      */
     public static class State {
         /** When session is ready to initiate a call or transaction. */
@@ -101,7 +99,6 @@
 
     /**
      * Listener class that listens to {@link SipSession} events.
-     * @hide
      */
     public static class Listener {
         /**
@@ -281,7 +278,7 @@
 
     /**
      * Gets the session state. The value returned must be one of the states in
-     * {@link SipSessionState}.
+     * {@link State}.
      *
      * @return the session state
      */
@@ -339,7 +336,7 @@
      * Performs registration to the server specified by the associated local
      * profile. The session listener is called back upon success or failure of
      * registration. The method is only valid to call when the session state is
-     * in {@link SipSessionState#READY_TO_CALL}.
+     * in {@link State#READY_TO_CALL}.
      *
      * @param duration duration in second before the registration expires
      * @see Listener
@@ -357,7 +354,7 @@
      * profile. Unregistration is technically the same as registration with zero
      * expiration duration. The session listener is called back upon success or
      * failure of unregistration. The method is only valid to call when the
-     * session state is in {@link SipSessionState#READY_TO_CALL}.
+     * session state is in {@link State#READY_TO_CALL}.
      *
      * @see Listener
      */
@@ -372,7 +369,7 @@
     /**
      * Initiates a call to the specified profile. The session listener is called
      * back upon defined session events. The method is only valid to call when
-     * the session state is in {@link SipSessionState#READY_TO_CALL}.
+     * the session state is in {@link State#READY_TO_CALL}.
      *
      * @param callee the SIP profile to make the call to
      * @param sessionDescription the session description of this call
@@ -393,7 +390,7 @@
     /**
      * Answers an incoming call with the specified session description. The
      * method is only valid to call when the session state is in
-     * {@link SipSessionState#INCOMING_CALL}.
+     * {@link State#INCOMING_CALL}.
      *
      * @param sessionDescription the session description to answer this call
      * @param timeout the session will be timed out if the call is not
@@ -411,10 +408,10 @@
     /**
      * Ends an established call, terminates an outgoing call or rejects an
      * incoming call. The method is only valid to call when the session state is
-     * in {@link SipSessionState#IN_CALL},
-     * {@link SipSessionState#INCOMING_CALL},
-     * {@link SipSessionState#OUTGOING_CALL} or
-     * {@link SipSessionState#OUTGOING_CALL_RING_BACK}.
+     * in {@link State#IN_CALL},
+     * {@link State#INCOMING_CALL},
+     * {@link State#OUTGOING_CALL} or
+     * {@link State#OUTGOING_CALL_RING_BACK}.
      */
     public void endCall() {
         try {
@@ -426,7 +423,7 @@
 
     /**
      * Changes the session description during a call. The method is only valid
-     * to call when the session state is in {@link SipSessionState#IN_CALL}.
+     * to call when the session state is in {@link State#IN_CALL}.
      *
      * @param sessionDescription the new session description
      * @param timeout the session will be timed out if the call is not
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 1fa2400..db1931b 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -123,6 +123,8 @@
     }
 
     public synchronized SipProfile[] getListOfProfiles() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         boolean isCallerRadio = isCallerRadio();
         ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
         for (SipSessionGroupExt group : mSipGroups.values()) {
@@ -134,6 +136,8 @@
     }
 
     public void open(SipProfile localProfile) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         localProfile.setCallingUid(Binder.getCallingUid());
         try {
             createGroup(localProfile);
@@ -146,6 +150,8 @@
     public synchronized void open3(SipProfile localProfile,
             PendingIntent incomingCallPendingIntent,
             ISipSessionListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         localProfile.setCallingUid(Binder.getCallingUid());
         if (incomingCallPendingIntent == null) {
             Log.w(TAG, "incomingCallPendingIntent cannot be null; "
@@ -159,7 +165,7 @@
                     incomingCallPendingIntent, listener);
             if (localProfile.getAutoRegistration()) {
                 group.openToReceiveCalls();
-                if (isWifiOn()) grabWifiLock();
+                if (isWifiActive()) grabWifiLock();
             }
         } catch (SipException e) {
             Log.e(TAG, "openToReceiveCalls()", e);
@@ -181,6 +187,8 @@
     }
 
     public synchronized void close(String localProfileUri) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
         if (group == null) return;
         if (!isCallerCreatorOrRadio(group)) {
@@ -191,10 +199,12 @@
         group = mSipGroups.remove(localProfileUri);
         notifyProfileRemoved(group.getLocalProfile());
         group.close();
-        if (isWifiOn() && !anyOpened()) releaseWifiLock();
+        if (isWifiActive() && !anyOpened()) releaseWifiLock();
     }
 
     public synchronized boolean isOpened(String localProfileUri) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
         if (group == null) return false;
         if (isCallerCreatorOrRadio(group)) {
@@ -206,6 +216,8 @@
     }
 
     public synchronized boolean isRegistered(String localProfileUri) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
         if (group == null) return false;
         if (isCallerCreatorOrRadio(group)) {
@@ -218,6 +230,8 @@
 
     public synchronized void setRegistrationListener(String localProfileUri,
             ISipSessionListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
         if (group == null) return;
         if (isCallerCreator(group)) {
@@ -229,6 +243,8 @@
 
     public synchronized ISipSession createSession(SipProfile localProfile,
             ISipSessionListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         localProfile.setCallingUid(Binder.getCallingUid());
         if (!mConnected) return null;
         try {
@@ -241,6 +257,8 @@
     }
 
     public synchronized ISipSession getPendingSession(String callId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         if (callId == null) return null;
         return mPendingSessions.get(callId);
     }
@@ -330,9 +348,8 @@
         }
     }
 
-    private boolean isWifiOn() {
+    private boolean isWifiActive() {
         return "WIFI".equalsIgnoreCase(mNetworkType);
-        //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType));
     }
 
     private synchronized void onConnectivityChanged(
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
index a1a7aed..ad63cd6 100644
--- a/voip/jni/rtp/EchoSuppressor.cpp
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -157,11 +157,12 @@
     if (correlation > 0.3f) {
         float factor = 1.0f - correlation;
         factor *= factor;
+        factor /= 2.0; // suppress harder
         for (int i = 0; i < mSampleCount; ++i) {
             recorded[i] *= factor;
         }
     }
-//    LOGI("latency %5d, correlation %.10f", latency, correlation);
+    //LOGI("latency %5d, correlation %.10f", latency, correlation);
 
 
     // Increase RecordOffset.