Merge "fix getmacaddress race condition (don't auto-merge)" into gingerbread
diff --git a/api/current.xml b/api/current.xml
index 84ccd45..2a2200d 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"
@@ -24538,7 +24549,7 @@
 </parameter>
 </method>
 <method name="remove"
- return="void"
+ return="int"
  abstract="false"
  native="false"
  synchronized="false"
@@ -24547,7 +24558,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="id" type="long">
+<parameter name="ids" type="long...">
 </parameter>
 </method>
 <field name="ACTION_DOWNLOAD_COMPLETE"
@@ -24940,7 +24951,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="id" type="long">
+<parameter name="ids" type="long...">
 </parameter>
 </method>
 <method name="setFilterByStatus"
@@ -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"
@@ -123867,6 +125837,8 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<implements name="java.io.Closeable">
+</implements>
 <implements name="android.os.Parcelable">
 </implements>
 <constructor name="DropBoxManager.Entry"
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/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e93e684..c08f1fc 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2739,14 +2739,15 @@
         private final File mBackupFile;
         private final int mMode;
 
-        private Map<String, Object> mMap;  // guarded by 'this'
-        private long mTimestamp;  // guarded by 'this'
+        private Map<String, Object> mMap;     // guarded by 'this'
         private int mDiskWritesInFlight = 0;  // guarded by 'this'
-        private boolean mLoaded = false;  // guarded by 'this'
+        private boolean mLoaded = false;      // guarded by 'this'
+        private long mStatTimestamp;          // guarded by 'this'
+        private long mStatSize;               // guarded by 'this'
 
         private final Object mWritingToDiskLock = new Object();
         private static final Object mContent = new Object();
-        private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
+        private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
 
         SharedPreferencesImpl(
             File file, int mode, Map initialContents) {
@@ -2757,7 +2758,7 @@
             mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
             FileStatus stat = new FileStatus();
             if (FileUtils.getFileStatus(file.getPath(), stat)) {
-                mTimestamp = stat.mtime;
+                mStatTimestamp = stat.mtime;
             }
             mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
         }
@@ -2775,7 +2776,7 @@
             synchronized (this) {
                 if (mDiskWritesInFlight > 0) {
                     // If we know we caused it, it's not unexpected.
-                    Log.d(TAG, "disk write in flight, not unexpected.");
+                    if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
                     return false;
                 }
             }
@@ -2784,7 +2785,7 @@
                 return true;
             }
             synchronized (this) {
-                return mTimestamp != stat.mtime;
+                return mStatTimestamp != stat.mtime || mStatSize != stat.size;
             }
         }
 
@@ -3165,7 +3166,8 @@
                 FileStatus stat = new FileStatus();
                 if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
                     synchronized (this) {
-                        mTimestamp = stat.mtime;
+                        mStatTimestamp = stat.mtime;
+                        mStatSize = stat.size;
                     }
                 }
                 // Writing was successful, delete the backup file if there is one.
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index c476b8f..6256303 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -567,18 +567,18 @@
          */
         public static final int ORDER_DESCENDING = 2;
 
-        private Long mId = null;
+        private long[] mIds = null;
         private Integer mStatusFlags = null;
         private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
         private int mOrderDirection = ORDER_DESCENDING;
         private boolean mOnlyIncludeVisibleInDownloadsUi = false;
 
         /**
-         * Include only the download with the given ID.
+         * Include only the downloads with the given IDs.
          * @return this object
          */
-        public Query setFilterById(long id) {
-            mId = id;
+        public Query setFilterById(long... ids) {
+            mIds = ids;
             return this;
         }
 
@@ -639,9 +639,11 @@
         Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
             Uri uri = baseUri;
             List<String> selectionParts = new ArrayList<String>();
+            String[] selectionArgs = null;
 
-            if (mId != null) {
-                uri = ContentUris.withAppendedId(uri, mId);
+            if (mIds != null) {
+                selectionParts.add(getWhereClauseForIds(mIds));
+                selectionArgs = getWhereArgsForIds(mIds);
             }
 
             if (mStatusFlags != null) {
@@ -676,7 +678,7 @@
             String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
             String orderBy = mOrderByColumn + " " + orderDirection;
 
-            return resolver.query(uri, projection, selection, null, orderBy);
+            return resolver.query(uri, projection, selection, selectionArgs, orderBy);
         }
 
         private String joinStrings(String joiner, Iterable<String> parts) {
@@ -738,17 +740,28 @@
     }
 
     /**
-     * Cancel a download and remove it from the download manager.  The download will be stopped if
+     * Cancel downloads and remove them from the download manager.  Each download will be stopped if
      * it was running, and it will no longer be accessible through the download manager.  If a file
-     * was already downloaded, it will not be deleted.
+     * was already downloaded to external storage, it will not be deleted.
      *
-     * @param id the ID of the download
+     * @param ids the IDs of the downloads to remove
+     * @return the number of downloads actually removed
      */
-    public void remove(long id) {
-        int numDeleted = mResolver.delete(getDownloadUri(id), null, null);
-        if (numDeleted == 0) {
-            throw new IllegalArgumentException("Download " + id + " does not exist");
+    public int remove(long... ids) {
+        StringBuilder whereClause = new StringBuilder();
+        String[] whereArgs = new String[ids.length];
+
+        whereClause.append(Downloads.Impl._ID + " IN (");
+        for (int i = 0; i < ids.length; i++) {
+            if (i > 0) {
+                whereClause.append(",");
+            }
+            whereClause.append("?");
+            whereArgs[i] = Long.toString(ids[i]);
         }
+        whereClause.append(")");
+
+        return mResolver.delete(mBaseUri, whereClause.toString(), whereArgs);
     }
 
     /**
@@ -776,20 +789,20 @@
     }
 
     /**
-     * Restart the given download, which must have already completed (successfully or not).  This
+     * Restart the given downloads, which must have already completed (successfully or not).  This
      * method will only work when called from within the download manager's process.
-     * @param id the ID of the download
+     * @param ids the IDs of the downloads
      * @hide
      */
-    public void restartDownload(long id) {
-        Cursor cursor = query(new Query().setFilterById(id));
+    public void restartDownload(long... ids) {
+        Cursor cursor = query(new Query().setFilterById(ids));
         try {
-            if (!cursor.moveToFirst()) {
-                throw new IllegalArgumentException("No download with id " + id);
-            }
-            int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
-            if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
-                throw new IllegalArgumentException("Cannot restart incomplete download: " + id);
+            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+                int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
+                if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
+                    throw new IllegalArgumentException("Cannot restart incomplete download: "
+                            + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
+                }
             }
         } finally {
             cursor.close();
@@ -800,7 +813,7 @@
         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
         values.putNull(Downloads.Impl._DATA);
         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
-        mResolver.update(getDownloadUri(id), values, null, null);
+        mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
     }
 
     /**
@@ -811,6 +824,33 @@
     }
 
     /**
+     * Get a parameterized SQL WHERE clause to select a bunch of IDs.
+     */
+    static String getWhereClauseForIds(long[] ids) {
+        StringBuilder whereClause = new StringBuilder();
+        whereClause.append(Downloads.Impl._ID + " IN (");
+        for (int i = 0; i < ids.length; i++) {
+            if (i > 0) {
+                whereClause.append(",");
+            }
+            whereClause.append("?");
+        }
+        whereClause.append(")");
+        return whereClause.toString();
+    }
+
+    /**
+     * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
+     */
+    static String[] getWhereArgsForIds(long[] ids) {
+        String[] whereArgs = new String[ids.length];
+        for (int i = 0; i < ids.length; i++) {
+            whereArgs[i] = Long.toString(ids[i]);
+        }
+        return whereArgs;
+    }
+
+    /**
      * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
      * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
      * Some columns correspond directly to underlying values while others are computed from
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/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 8a52e40..3447e76c 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.ContextMenu;
 import android.view.inputmethod.ExtractedText;
 import android.widget.EditText;
 
@@ -29,7 +28,6 @@
 public class ExtractEditText extends EditText {
     private InputMethodService mIME;
     private int mSettingExtractedText;
-    private boolean mContextMenuShouldBeHandledBySuper = false;
     
     public ExtractEditText(Context context) {
         super(context, null);
@@ -99,19 +97,13 @@
         return false;
     }
     
-    @Override
-    protected void onCreateContextMenu(ContextMenu menu) {
-        super.onCreateContextMenu(menu);
-        mContextMenuShouldBeHandledBySuper = true;
-    }
-
     @Override public boolean onTextContextMenuItem(int id) {
-        if (mIME != null && !mContextMenuShouldBeHandledBySuper) {
+        // Horrible hack: select word option has to be handled by original view to work.
+        if (mIME != null && id != android.R.id.startSelectingText) {
             if (mIME.onExtractTextContextMenuItem(id)) {
                 return true;
             }
         }
-        mContextMenuShouldBeHandledBySuper = false;
         return super.onTextContextMenuItem(id);
     }
     
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d49c8be..d67e6f5 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -449,6 +449,10 @@
         public static final int STATE_WAKE_LOCK_FLAG = 1<<17;
         public static final int STATE_SENSOR_ON_FLAG = 1<<16;
         
+        public static final int MOST_INTERESTING_STATES =
+            STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG
+            | STATE_GPS_ON_FLAG | STATE_PHONE_IN_CALL_FLAG;
+
         public int states;
 
         public HistoryItem() {
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index 23fdb0b..a47c66a 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -21,6 +21,7 @@
 import com.android.internal.os.IDropBoxManagerService;
 
 import java.io.ByteArrayInputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -61,7 +62,7 @@
      * This may include a reference to a stream, so you must call
      * {@link #close()} when you are done using it.
      */
-    public static class Entry implements Parcelable {
+    public static class Entry implements Parcelable, Closeable {
         private final String mTag;
         private final long mTimeMillis;
 
@@ -152,7 +153,7 @@
         /** @return time when the entry was originally created. */
         public long getTimeMillis() { return mTimeMillis; }
 
-        /** @return flags describing the content returned by @{link #getInputStream()}. */
+        /** @return flags describing the content returned by {@link #getInputStream()}. */
         public int getFlags() { return mFlags & ~IS_GZIPPED; }  // getInputStream() decompresses.
 
         /**
@@ -288,8 +289,8 @@
     }
 
     /**
-     * Gets the next entry from the drop box *after* the specified time.
-     * Requires android.permission.READ_LOGS.  You must always call
+     * Gets the next entry from the drop box <em>after</em> the specified time.
+     * Requires <code>android.permission.READ_LOGS</code>.  You must always call
      * {@link Entry#close()} on the return value!
      *
      * @param tag of entry to look for, null for all tags
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 6237c0e..0090177 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -34,16 +34,19 @@
     Message mMessages;
     private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
     private IdleHandler[] mPendingIdleHandlers;
-    private boolean mQuiting = false;
+    private boolean mQuiting;
     boolean mQuitAllowed = true;
 
+    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
+    private boolean mBlocked;
+
     @SuppressWarnings("unused")
     private int mPtr; // used by native code
     
     private native void nativeInit();
     private native void nativeDestroy();
-    private native boolean nativePollOnce(int timeoutMillis);
-    private native void nativeWake();
+    private native void nativePollOnce(int ptr, int timeoutMillis);
+    private native void nativeWake(int ptr);
 
     /**
      * Callback interface for discovering when a thread is going to block
@@ -113,7 +116,7 @@
             if (nextPollTimeoutMillis != 0) {
                 Binder.flushPendingCommands();
             }
-            nativePollOnce(nextPollTimeoutMillis);
+            nativePollOnce(mPtr, nextPollTimeoutMillis);
 
             synchronized (this) {
                 // Try to retrieve the next message.  Return if found.
@@ -122,7 +125,9 @@
                 if (msg != null) {
                     final long when = msg.when;
                     if (now >= when) {
+                        mBlocked = false;
                         mMessages = msg.next;
+                        msg.next = null;
                         if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
                         return msg;
                     } else {
@@ -138,6 +143,7 @@
                 }
                 if (pendingIdleHandlerCount == 0) {
                     // No idle handlers to run.  Loop and wait some more.
+                    mBlocked = true;
                     continue;
                 }
 
@@ -184,6 +190,7 @@
         if (msg.target == null && !mQuitAllowed) {
             throw new RuntimeException("Main thread not allowed to quit");
         }
+        final boolean needWake;
         synchronized (this) {
             if (mQuiting) {
                 RuntimeException e = new RuntimeException(
@@ -200,6 +207,7 @@
             if (p == null || when == 0 || when < p.when) {
                 msg.next = p;
                 mMessages = msg;
+                needWake = mBlocked; // new head, might need to wake up
             } else {
                 Message prev = null;
                 while (p != null && p.when <= when) {
@@ -208,9 +216,12 @@
                 }
                 msg.next = prev.next;
                 prev.next = msg;
+                needWake = false; // still waiting on head, no need to wake up
             }
         }
-        nativeWake();
+        if (needWake) {
+            nativeWake(mPtr);
+        }
         return true;
     }
 
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 9e7eedf..912180b 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -195,7 +195,8 @@
     public static final int KEYCODE_TAB             = 61;
     /** Key code constant: Space key. */
     public static final int KEYCODE_SPACE           = 62;
-    /** Key code constant: Symbol modifier key. */
+    /** Key code constant: Symbol modifier key.
+     * Used to enter alternate symbols. */
     public static final int KEYCODE_SYM             = 63;
     /** Key code constant: Explorer special function key.
      * Used to launch a browser application. */
@@ -205,7 +206,8 @@
     public static final int KEYCODE_ENVELOPE        = 65;
     /** Key code constant: Enter key. */
     public static final int KEYCODE_ENTER           = 66;
-    /** Key code constant: Delete key. */
+    /** Key code constant: Backspace key.
+     * Deletes characters before the insertion point. */
     public static final int KEYCODE_DEL             = 67;
     /** Key code constant: '`' (backtick) key. */
     public static final int KEYCODE_GRAVE           = 68;
@@ -227,7 +229,10 @@
     public static final int KEYCODE_SLASH           = 76;
     /** Key code constant: '@' key. */
     public static final int KEYCODE_AT              = 77;
-    /** Key code constant: Number Lock modifier key. */
+    /** Key code constant: Number modifier key.
+     * Used to enter numeric symbols.
+     * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
+     * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
     public static final int KEYCODE_NUM             = 78;
     /** Key code constant: Headset Hook key.
      * Used to hang up calls and stop media. */
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..c1a389a 100644
--- a/core/java/android/widget/EdgeGlow.java
+++ b/core/java/android/widget/EdgeGlow.java
@@ -38,7 +38,7 @@
     // Time it will take for a pulled glow to decay to partial strength before release
     private static final int PULL_DECAY_TIME = 10000;
 
-    private static final float MAX_ALPHA = 1.f;
+    private static final float MAX_ALPHA = 0.8f;
     private static final float HELD_EDGE_ALPHA = 0.7f;
     private static final float HELD_EDGE_SCALE_Y = 0.5f;
     private static final float HELD_GLOW_ALPHA = 0.5f;
@@ -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,9 @@
         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(MAX_ALPHA,
+                mGlowAlpha +
+                (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
 
         float glowChange = Math.abs(deltaDistance);
         if (deltaDistance > 0 && mPullDistance < 0) {
@@ -154,8 +156,10 @@
         if (mPullDistance == 0) {
             mGlowScaleY = 0;
         }
-        mGlowScaleY = mGlowScaleYStart = Math.max(
-                0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR);
+
+        // Do not allow glow to get larger than MAX_GLOW_HEIGHT.
+        mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max(
+                0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR));
 
         mEdgeAlphaFinish = mEdgeAlpha;
         mEdgeScaleYFinish = mEdgeScaleY;
@@ -202,8 +206,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 +217,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 +286,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 +302,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/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 055ba87..76755de 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -109,6 +109,7 @@
     private Drawable mBelowAnchorBackgroundDrawable;
 
     private boolean mAboveAnchor;
+    private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
     
     private OnDismissListener mOnDismissListener;
     private boolean mIgnoreCheekPress = false;
@@ -623,6 +624,25 @@
     }
 
     /**
+     * Set the layout type for this window. Should be one of the TYPE constants defined in
+     * {@link WindowManager.LayoutParams}.
+     *
+     * @param layoutType Layout type for this window.
+     * @hide
+     */
+    public void setWindowLayoutType(int layoutType) {
+        mWindowLayoutType = layoutType;
+    }
+
+    /**
+     * @return The layout type for this window.
+     * @hide
+     */
+    public int getWindowLayoutType() {
+        return mWindowLayoutType;
+    }
+
+    /**
      * <p>Change the width and height measure specs that are given to the
      * window manager by the popup.  By default these are 0, meaning that
      * the current width or height is requested as an explicit size from
@@ -911,7 +931,7 @@
             p.format = PixelFormat.TRANSLUCENT;
         }
         p.flags = computeFlags(p.flags);
-        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        p.type = mWindowLayoutType;
         p.token = token;
         p.softInputMode = mSoftInputMode;
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
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 3694b37..138aefa 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -96,6 +96,7 @@
 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;
@@ -4320,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) {
@@ -4352,6 +4354,7 @@
                 break;
                 
             case KeyEvent.KEYCODE_DPAD_CENTER:
+                mDPadCenterIsDown = true;
                 if (shouldAdvanceFocusOnEnter()) {
                     return 0;
                 }
@@ -4446,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.
@@ -4466,6 +4470,7 @@
                 return super.onKeyUp(keyCode, event);
                 
             case KeyEvent.KEYCODE_ENTER:
+                mEnterKeyIsDown = false;
                 if (mInputContentType != null
                         && mInputContentType.onEditorActionListener != null
                         && mInputContentType.enterDown) {
@@ -6544,11 +6549,11 @@
             int selEnd = getSelectionEnd();
 
             if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
+                // If a tap was used to give focus to that view, move cursor at tap position.
                 // Has to be done before onTakeFocus, which can be overloaded.
-                if (mLastTouchOffset >= 0) {
-                    // Can happen when a TextView is displayed after its content has been deleted.
-                    mLastTouchOffset = Math.min(mLastTouchOffset, mText.length());
-                    Selection.setSelection((Spannable) mText, mLastTouchOffset);
+                final int lastTapPosition = getLastTapPosition();
+                if (lastTapPosition >= 0) {
+                    Selection.setSelection((Spannable) mText, lastTapPosition);
                 }
 
                 if (mMovement != null) {
@@ -6609,7 +6614,9 @@
                 terminateTextSelectionMode();
             }
 
-            mLastTouchOffset = -1;
+            if (mSelectionModifierCursorController != null) {
+                ((SelectionModifierCursorController) mSelectionModifierCursorController).resetTouchOffsets();
+            }
         }
 
         startStopMarquee(focused);
@@ -6621,6 +6628,24 @@
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
     }
 
+    private int getLastTapPosition() {
+        if (mSelectionModifierCursorController != null) {
+            int lastTapPosition = ((SelectionModifierCursorController)
+                    mSelectionModifierCursorController).getMinTouchOffset();
+            if (lastTapPosition >= 0) {
+                // Safety check, should not be possible.
+                if (lastTapPosition > mText.length()) {
+                    Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
+                            + mText.length() + ")");
+                    lastTapPosition = mText.length();
+                }
+                return lastTapPosition;
+            }
+        }
+
+        return -1;
+    }
+
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
@@ -6712,7 +6737,7 @@
                 // Tapping outside stops selection mode, if any
                 stopTextSelectionMode();
 
-                if (mInsertionPointCursorController != null) {
+                if (mInsertionPointCursorController != null && mText.length() > 0) {
                     mInsertionPointCursorController.show();
                 }
             }
@@ -7226,9 +7251,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
@@ -7236,24 +7273,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 (mContextMenuTriggeredByKey) {
+            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());
         }
@@ -7262,14 +7306,11 @@
     }
     
     private String getWordForDictionary() {
-        if (mLastTouchOffset < 0) {
-            return null;
-        }
-
-        long wordLimits = getWordLimitsAt(mLastTouchOffset);
+        int seedPosition = mContextMenuTriggeredByKey ? getSelectionStart() : getLastTapPosition();
+        long wordLimits = getWordLimitsAt(seedPosition);
         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;
@@ -7316,6 +7357,14 @@
     protected void onCreateContextMenu(ContextMenu menu) {
         super.onCreateContextMenu(menu);
         boolean added = false;
+        mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
+        // Problem with context menu on long press: the menu appears while the key in down and when
+        // the key is released, the view does not receive the key_up event. This ensures that the
+        // state is reset whenever the context menu action is displayed.
+        // mContextMenuTriggeredByKey saved that state so that it is available in
+        // onTextContextMenuItem. We cannot simply clear these flags in onTextContextMenuItem since
+        // it may not be called (if the user/ discards the context menu with the back key).
+        mDPadCenterIsDown = mEnterKeyIsDown = false;
 
         if (mIsInTextSelectionMode) {
             MenuHandler handler = new MenuHandler();
@@ -7341,21 +7390,6 @@
                 added = true;
             }
         } else {
-            /*
-            if (!isFocused()) {
-                if (isFocusable() && mInput != null) {
-                    if (canCopy()) {
-                        MenuHandler handler = new MenuHandler();
-                        menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
-                             setOnMenuItemClickListener(handler).
-                             setAlphabeticShortcut('c');
-                        menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
-                    }
-                }
-
-                //return;
-            }
-             */
             MenuHandler handler = new MenuHandler();
 
             if (canSelectText()) {
@@ -7484,43 +7518,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();
@@ -7543,7 +7543,6 @@
 
             case ID_ADD_TO_DICTIONARY:
                 String word = getWordForDictionary();
-
                 if (word != null) {
                     Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
                     i.putExtra("word", word);
@@ -7556,6 +7555,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()) {
@@ -7675,6 +7717,7 @@
                     com.android.internal.R.attr.textSelectHandleWindowStyle);
             mContainer.setSplitTouchEnabled(true);
             mContainer.setClippingEnabled(false);
+            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
 
             setOrientation(pos);
         }
@@ -7858,8 +7901,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;
             }
@@ -7984,6 +8026,7 @@
         SelectionModifierCursorController() {
             mStartHandle = new HandleView(this, HandleView.LEFT);
             mEndHandle = new HandleView(this, HandleView.RIGHT);
+            resetTouchOffsets();
         }
 
         public void show() {
@@ -8066,14 +8109,16 @@
         }
 
         public boolean onTouchEvent(MotionEvent event) {
-            if (isFocused() && isTextEditable()) {
+            // This is done even when the View does not have focus, so that long presses can start
+            // selection and tap can move cursor from this tap position.
+            if (isTextEditable()) {
                 switch (event.getActionMasked()) {
                     case MotionEvent.ACTION_DOWN:
                         final int x = (int) event.getX();
                         final int y = (int) event.getY();
 
                         // Remember finger down position, to be able to start selection from there
-                        mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y);
+                        mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
 
                         break;
 
@@ -8113,6 +8158,10 @@
             return mMaxTouchOffset;
         }
 
+        public void resetTouchOffsets() {
+            mMinTouchOffset = mMaxTouchOffset = -1;
+        }
+
         /**
          * @return true iff this controller is currently used to move the selection start.
          */
@@ -8230,7 +8279,11 @@
     private CursorController        mInsertionPointCursorController;
     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;
+    private boolean                 mContextMenuTriggeredByKey = 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/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b033fad..a9e5052 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -72,6 +72,9 @@
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
     
+    // No, really, THIS is the maximum number of items we will record in the history.
+    private static final int MAX_MAX_HISTORY_ITEMS = 3000;
+
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
     // in to one common name.
@@ -1169,15 +1172,20 @@
         mBtHeadset = headset;
     }
 
+    int mChangedStates = 0;
+
     void addHistoryRecordLocked(long curTime) {
         if (!mHaveBatteryLevel || !mRecordingHistory) {
             return;
         }
 
         // If the current time is basically the same as the last time,
-        // just collapse into one record.
+        // and no states have since the last recorded entry changed and
+        // are now resetting back to their original value, then just collapse
+        // into one record.
         if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE
-                && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)) {
+                && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+2000)
+                && ((mHistoryEnd.states^mHistoryCur.states)&mChangedStates) == 0) {
             // If the current is the same as the one before, then we no
             // longer need the entry.
             if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
@@ -1188,20 +1196,29 @@
                 mHistoryEnd = mHistoryLastEnd;
                 mHistoryLastEnd = null;
             } else {
+                mChangedStates |= mHistoryEnd.states^mHistoryCur.states;
                 mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, mHistoryCur);
             }
             return;
         }
 
-        if (mNumHistoryItems == MAX_HISTORY_ITEMS) {
+        mChangedStates = 0;
+
+        if (mNumHistoryItems == MAX_HISTORY_ITEMS
+                || mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) {
             addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW);
         }
 
         if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
             // Once we've reached the maximum number of items, we only
+            // record changes to the battery level and the most interesting states.
+            // Once we've reached the maximum maximum number of items, we only
             // record changes to the battery level.
             if (mHistoryEnd != null && mHistoryEnd.batteryLevel
-                    == mHistoryCur.batteryLevel) {
+                    == mHistoryCur.batteryLevel &&
+                    (mNumHistoryItems >= MAX_MAX_HISTORY_ITEMS
+                            || ((mHistoryEnd.states^mHistoryCur.states)
+                                    & HistoryItem.MOST_INTERESTING_STATES) == 0)) {
                 return;
             }
         }
@@ -4450,10 +4467,13 @@
     }
 
     public void commitPendingDataToDisk() {
-        Parcel next;
+        final Parcel next;
         synchronized (this) {
             next = mPendingWrite;
             mPendingWrite = null;
+            if (next == null) {
+                return;
+            }
 
             mWriteLock.lock();
         }
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 1b203ca..d2e5462 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -41,7 +41,7 @@
 
     inline sp<Looper> getLooper() { return mLooper; }
 
-    bool pollOnce(int timeoutMillis);
+    void pollOnce(int timeoutMillis);
     void wake();
 
 private:
@@ -61,8 +61,8 @@
 NativeMessageQueue::~NativeMessageQueue() {
 }
 
-bool NativeMessageQueue::pollOnce(int timeoutMillis) {
-    return mLooper->pollOnce(timeoutMillis) != ALOOPER_POLL_TIMEOUT;
+void NativeMessageQueue::pollOnce(int timeoutMillis) {
+    mLooper->pollOnce(timeoutMillis);
 }
 
 void NativeMessageQueue::wake() {
@@ -112,24 +112,14 @@
     jniThrowException(env, "java/lang/IllegalStateException", "Message queue not initialized");
 }
 
-static jboolean android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
-        jint timeoutMillis) {
-    NativeMessageQueue* nativeMessageQueue =
-            android_os_MessageQueue_getNativeMessageQueue(env, obj);
-    if (! nativeMessageQueue) {
-        throwQueueNotInitialized(env);
-        return false;
-    }
-    return nativeMessageQueue->pollOnce(timeoutMillis);
+static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
+        jint ptr, jint timeoutMillis) {
+    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
+    nativeMessageQueue->pollOnce(timeoutMillis);
 }
 
-static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj) {
-    NativeMessageQueue* nativeMessageQueue =
-            android_os_MessageQueue_getNativeMessageQueue(env, obj);
-    if (! nativeMessageQueue) {
-        throwQueueNotInitialized(env);
-        return;
-    }
+static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) {
+    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
     return nativeMessageQueue->wake();
 }
 
@@ -139,8 +129,8 @@
     /* name, signature, funcPtr */
     { "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit },
     { "nativeDestroy", "()V", (void*)android_os_MessageQueue_nativeDestroy },
-    { "nativePollOnce", "(I)Z", (void*)android_os_MessageQueue_nativePollOnce },
-    { "nativeWake", "()V", (void*)android_os_MessageQueue_nativeWake }
+    { "nativePollOnce", "(II)V", (void*)android_os_MessageQueue_nativePollOnce },
+    { "nativeWake", "(I)V", (void*)android_os_MessageQueue_nativeWake }
 };
 
 #define FIND_CLASS(var, className) \
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-hdpi/popup_bottom_dark.9.png b/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
index 26500bb..8b5d3d5 100755
--- a/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_bottom_medium.9.png b/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
index 42e5ad5..26ede44 100755
--- a/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
+++ b/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_center_dark.9.png b/core/res/res/drawable-hdpi/popup_center_dark.9.png
index b0740a4..ac1f92d 100755
--- a/core/res/res/drawable-hdpi/popup_center_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_center_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_center_medium.9.png b/core/res/res/drawable-hdpi/popup_center_medium.9.png
index 1cca06d..1ce2a6d 100755
--- a/core/res/res/drawable-hdpi/popup_center_medium.9.png
+++ b/core/res/res/drawable-hdpi/popup_center_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_full_dark.9.png b/core/res/res/drawable-hdpi/popup_full_dark.9.png
index 155d7df..8f2fdf0 100755
--- a/core/res/res/drawable-hdpi/popup_full_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_full_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_top_dark.9.png b/core/res/res/drawable-hdpi/popup_top_dark.9.png
index 60e7e20..1108909 100755
--- a/core/res/res/drawable-hdpi/popup_top_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_top_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/screen_progress_frame.9.png b/core/res/res/drawable-hdpi/screen_progress_frame.9.png
deleted file mode 100644
index 3f9d738..0000000
--- a/core/res/res/drawable-hdpi/screen_progress_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/screen_progress_inner.9.png b/core/res/res/drawable-hdpi/screen_progress_inner.9.png
deleted file mode 100644
index 10c7da5..0000000
--- a/core/res/res/drawable-hdpi/screen_progress_inner.9.png
+++ /dev/null
Binary files differ
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/drawable-mdpi/screen_progress_frame.9.png b/core/res/res/drawable-mdpi/screen_progress_frame.9.png
deleted file mode 100644
index 0e92429..0000000
--- a/core/res/res/drawable-mdpi/screen_progress_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/screen_progress_inner.9.png b/core/res/res/drawable-mdpi/screen_progress_inner.9.png
deleted file mode 100644
index 1799a53..0000000
--- a/core/res/res/drawable-mdpi/screen_progress_inner.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/screen_progress.xml b/core/res/res/drawable/screen_progress.xml
deleted file mode 100644
index aed23a6..0000000
--- a/core/res/res/drawable/screen_progress.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/res/drawable/progress.xml
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@android:drawable/screen_progress_frame" />
-    <item>
-        <scale scaleWidth="100%" scaleGravity="0x3" drawable="@android:drawable/screen_progress_inner" />
-    </item>
-</layer-list>
-
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6e6dc26..340e23c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -352,4 +352,8 @@
 
     <!-- Enables SIP on WIFI only -->
     <bool name="config_sip_wifi_only">false</bool>
+
+    <!-- Boolean indicating if restoring network selection should be skipped -->
+    <!-- The restoring is handled by modem if it is true-->
+    <bool translatable="false" name="skip_restoring_network_selection">false</bool>
 </resources>
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/docs/html/guide/developing/debug-tasks.jd b/docs/html/guide/developing/debug-tasks.jd
index 500ef58..f0bf84c 100644
--- a/docs/html/guide/developing/debug-tasks.jd
+++ b/docs/html/guide/developing/debug-tasks.jd
@@ -40,10 +40,13 @@
   your application, which can help you profile the performance of your application.</dd>
   <dt><strong><a href="{@docRoot}guide/developing/tools/ddms.html#logcat">logcat</a></strong></dt>
   <dd>Dumps a log of system
-      messages. The messages include a stack trace when the emulator throws an error,
+      messages. The messages include a stack trace when the device throws an error,
       as well as {@link android.util.Log} messages you've written from your application. To run
-      logcat, execute <code>adb logcat</code> or, from DDMS, select <strong>Device > Run
-      logcat</strong>.
+      logcat, execute <code>adb logcat</code> from your Android SDK {@code tools/} directory or,
+from DDMS, select <strong>Device > Run
+      logcat</strong>. When using the <a href="{@docRoot}sdk/eclipse-adt.html">ADT plugin for
+Eclipse</a>, you can also view logcat messages by opening the Logcat view, available from
+<strong>Window > Show View > Other > Android > Logcat</strong>.
       <p>{@link android.util.Log} is a logging
       class you can use to print out messages to the logcat. You can read messages
       in real time if you run logcat on DDMS (covered next). Common logging methods include:
@@ -148,72 +151,7 @@
 
 <h2 id="DebuggingWebPages">Debugging Web Pages</h2>
 
-<p>If you're developing a web application for Android devices, you can debug your JavaScript in the
-Android Browser using the Console APIs, which will output messages to logcat. If you're familiar
-debugging web pages with Firefox's FireBug or WebKit's Web Inspector, then you're probably familiar
-with the Console APIs. The Android Browser (and the {@link android.webkit.WebChromeClient}) supports
-most of the same APIs.</p>
-
-<p>When you call a function from the Console APIs (in the DOM's {@code window.console} object),
-you will see the output in logcat as a warning. For example, if your web page
-executes the following JavaScript:</p>
-<pre class="no-pretty-print">
-console.log("Hello World");
-</pre>
-<p>Then the logcat output from the Android Browser will look like this:</p>
-<pre class="no-pretty-print">
-W/browser ( 202): Console: Hello World http://www.example.com/hello.html :82
-</pre>
-
-<p>All Console messages from the Android Browser are tagged with the name "browser" on Android
-platforms running API Level 7 or higher. On platforms running API Level 6 or lower, Browser
-messages are tagged with the name "WebCore". The Android Browser also formats console messages
-with the log message
-preceded by "Console:" and then followed by the address and line number where the
-message occurred. (The format for the address and line number will appear different from the example
-above on platforms running API Level 6 or lower.)</p>
-
-<p>The Android Browser (and {@link android.webkit.WebChromeClient}) does not implement all of the
-Console APIs provided by Firefox or other WebKit-based browsers. Primarily, you need to depend
-on the basic text logging functions:</p>
-<ul>
-  <li>{@code console.log(String)}</li>
-  <li>{@code console.info(String)}</li>
-  <li>{@code console.warn(String)}</li>
-  <li>{@code console.error(String)}</li>
-</ul>
-<p>Although the Android Browser may not fully implement other Console functions, they will not raise
-run-time errors, but may not behave the same as they do on other desktop browsers.</p>
-
-<p>If you've implemented a custom {@link android.webkit.WebView} in your application, then in order
-to receive messages that are sent through the Console APIs, you must provide a {@link
-android.webkit.WebChromeClient} that implements the {@link
-android.webkit.WebChromeClient#onConsoleMessage(String,int,String) onConsoleMessage()} callback
-method. For example, assuming that the {@code myWebView} field references the {@link
-android.webkit.WebView} in your application, you can log debug messages like this:</p>
-<pre>
-myWebView.setWebChromeClient(new WebChromeClient() {
-  public void onConsoleMessage(String message, int lineNumber, String sourceID) {
-    Log.d("MyApplication", message + " -- From line " + lineNumber + " of " + sourceID);
-  }
-});
-</pre>
-<p>The {@link android.webkit.WebChromeClient#onConsoleMessage(String,int,String)
-onConsoleMessage()} method will be called each time one of the Console methods is called from
-within your {@link android.webkit.WebView}.</p>
-<p>When the "Hello World" log is executed through your {@link android.webkit.WebView}, it will
-now look like this:</p>
-<pre class="no-pretty-print">
-D/MyApplication ( 430): Hello World -- From line 82 of http://www.example.com/hello.html
-</pre>
-
-<p class="note"><strong>Note:</strong> The {@link
-android.webkit.WebChromeClient#onConsoleMessage(String,int,String) onConsoleMessage()} callback
-method was added with API Level 7. If you are using a custom {@link
-android.webkit.WebView} on a platform running API Level 6 or lower, then your Console messages will
-automatically be sent to logcat with the "WebCore" logging tag.</p>
-
-
+<p>See the <a href="{@docRoot}guide/webapps/debugging.html">Debugging Web Apps</a> document.</p>
 
 
 <h2 id="toptips">Top Debugging Tips</h2>
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 43a5e8d..cdf5feb 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -110,7 +110,7 @@
       <li class="toggle-list">
         <div><a href="<?cs var:toroot ?>guide/topics/resources/index.html">
                <span class="en">Application Resources</span>
-             </a> <span class="new">new!</span></div>
+             </a></div>
         <ul>
           <li><a href="<?cs var:toroot ?>guide/topics/resources/providing-resources.html">
                 <span class="en">Providing Resources</span>
@@ -120,14 +120,14 @@
               </a></li>
           <li><a href="<?cs var:toroot ?>guide/topics/resources/runtime-changes.html">
                 <span class="en">Handling Runtime Changes</span>
-              </a> <span class="new">new!</span></li>
+              </a></li>
           <li><a href="<?cs var:toroot ?>guide/topics/resources/localization.html">
                 <span class="en">Localization</span>
               </a></li>
           <li class="toggle-list">
             <div><a href="<?cs var:toroot ?>guide/topics/resources/available-resources.html">
               <span class="en">Resource Types</span>
-            </a> <span class="new">new!</span></div>
+            </a></div>
             <ul>
               <li><a href="<?cs var:toroot ?>guide/topics/resources/animation-resource.html">Animation</a></li>
               <li><a href="<?cs var:toroot ?>guide/topics/resources/color-list-resource.html">Color State List</a></li>
@@ -147,11 +147,11 @@
       <li class="toggle-list">
         <div><a href="<?cs var:toroot ?>guide/topics/data/data-storage.html">
             <span class="en">Data Storage</span>
-          </a> <span class="new">new!</span></div>
+          </a></div>
           <ul>
             <li><a href="<?cs var:toroot ?>guide/topics/data/backup.html">
                 <span class="en">Data Backup</span>
-              </a> <span class="new">new!</span>
+              </a>
             </li>
           </ul>
       </li>
@@ -227,7 +227,7 @@
         <ul>
           <li><a href="<?cs var:toroot ?>guide/topics/location/obtaining-user-location.html">
                 <span class="en">Obtaining User Location</span>
-              </a> <span class="new">new!</span></li>
+              </a></li>
         </ul>
       </li>
   <!--<li class="toggle-list">
@@ -246,7 +246,7 @@
       <li class="toggle-list">
         <div><a href="<?cs var:toroot?>guide/topics/search/index.html">
             <span class="en">Search</span>
-          </a> <span class="new">new!</span></div>
+          </a></div>
           <ul>
             <li><a href="<?cs var:toroot?>guide/topics/search/search-dialog.html">Using the Android Search Dialog</a></li>
             <li><a href="<?cs var:toroot?>guide/topics/search/adding-recent-query-suggestions.html">Adding Recent Query Suggestions</a></li>
@@ -255,11 +255,11 @@
           </ul>
       </li>
       <li><a href="<?cs var:toroot?>guide/topics/testing/testing_android.html">
-            <span class="en">Testing and Instrumentation</span></a>
-            <span class="new">new!</span></li>
+            <span class="en">Testing and Instrumentation</span>
+          </a></li>
      <li><a href="<?cs var:toroot?>guide/topics/admin/device-admin.html">
-            <span class="en">Device Administration</span></a>
-            <span class="new">new!</span></li>
+            <span class="en">Device Administration</span>
+         </a> <span class="new">new!</span><!-- 10/8/10 --></li>
     </ul>
   </li>
 
@@ -306,18 +306,18 @@
            <div>
                 <a href="<?cs var:toroot ?>guide/developing/testing/index.html">
                    <span class="en">Testing</span>
-               </a> <span class="new">new!</span>
+               </a>
            </div>
            <ul>
               <li>
                 <a href="<?cs var:toroot ?>guide/developing/testing/testing_eclipse.html">
                   <span class="en">Testing in Eclipse, with ADT</span>
-                </a> <span class="new">new!</span>
+                </a>
               </li>
               <li>
                 <a href="<?cs var:toroot ?>guide/developing/testing/testing_otheride.html">
                   <span class="en">Testing in Other IDEs</span>
-                </a> <span class="new">new!</span>
+                </a>
               </li>
            </ul>
          </li>
@@ -332,8 +332,7 @@
       <!--<li><a href="<?cs var:toroot ?>guide/developing/tools/adt.html">ADT Plugin</a></li>-->
               <li><a href="<?cs var:toroot ?>guide/developing/tools/aidl.html">aidl</a></li>
               <li><a href="<?cs var:toroot ?>guide/developing/tools/avd.html">AVDs</a></li>
-              <li><a href="<?cs var:toroot ?>guide/developing/tools/bmgr.html">bmgr</a>
-            <span class="new">new!</span></li>
+              <li><a href="<?cs var:toroot ?>guide/developing/tools/bmgr.html">bmgr</a></li>
               <li><a href="<?cs var:toroot ?>guide/developing/tools/ddms.html">ddms</a></li>
               <li><a href="<?cs var:toroot ?>guide/developing/tools/othertools.html#dx">dx</a></li>
               <li><a href="<?cs var:toroot ?>guide/developing/tools/draw9patch.html">Draw 9-Patch</a></li>
@@ -413,7 +412,7 @@
     <ul>
       <li><a href="<?cs var:toroot ?>guide/practices/compatibility.html">
             <span class="en">Compatibility</span>
-          </a><span class="new">new!</span></li>
+          </a></li>
       <li><a href="<?cs var:toroot ?>guide/practices/screens_support.html">
             <span class="en">Supporting Multiple Screens</span>
           </a></li>
@@ -451,6 +450,25 @@
   </li>
 
   <li>
+    <h2><span class="en">Web Applications</span>
+    </h2>
+    <ul>
+      <li><a href="<?cs var:toroot ?>guide/webapps/targetting.html">
+            <span class="en">Targetting Android Devices</span>
+          </a> <span class="new">new!</span><!-- 10/8/10 --></li>
+      <li><a href="<?cs var:toroot ?>guide/webapps/webview.html">
+            <span class="en">Building Web Apps in WebView</span>
+          </a> <span class="new">new!</span><!-- 10/8/10 --></li>
+      <li><a href="<?cs var:toroot ?>guide/webapps/debugging.html">
+            <span class="en">Debugging Web Apps</span>
+          </a> <span class="new">new!</span><!-- 10/8/10 --></li>
+      <li><a href="<?cs var:toroot ?>guide/webapps/best-practices.html">
+            <span class="en">Best Practices for Web Apps</span>
+          </a> <span class="new">new!</span><!-- 10/8/10 --></li>
+    </ul>
+  </li>
+
+  <li>
     <h2><span class="en">Appendix</span>
                <span class="de" style="display:none">Anhang</span>
                <span class="es" style="display:none">Apéndice</span>
@@ -466,10 +484,10 @@
           </a></li>
       <li><a href="<?cs var:toroot ?>guide/appendix/market-filters.html">
             <span class="en">Market Filters</span>
-           </a> <span class="new">new!</span></li>
+           </a></li>
       <li><a href="<?cs var:toroot ?>guide/appendix/install-location.html">
             <span class="en">App Install Location</span>
-          </a> <span class="new">new!</span></li>
+          </a></li>
       <li><a href="<?cs var:toroot ?>guide/appendix/media-formats.html">
             <span class="en">Supported Media Formats</span>
           </a></li>
diff --git a/docs/html/guide/topics/admin/device-admin.jd b/docs/html/guide/topics/admin/device-admin.jd
index 4d9a14f..fda716a 100644
--- a/docs/html/guide/topics/admin/device-admin.jd
+++ b/docs/html/guide/topics/admin/device-admin.jd
@@ -1,24 +1,33 @@
-page.title=Android Device Administration API
+page.title=Device Administration
 @jd:body
+
 <div id="qv-wrapper">
 <div id="qv">
     <h2>In this document</h2>
     <ol>
 <li><a href="#overview">Device Administration API Overview</a>
     <ol>
+      <li><a href="#how">How does it work?</a></li>
       <li><a href="#policies">Policies</a></li>
     </ol>
   </li>
-  <li><a href="#how">How Does It Work?</a></li>
   <li><a href="#sample">Sample Application</a></li>
   <li><a href="#developing">Developing a Device Administration Application</a>
     <ol>
-      <li><a href="#manifest">Creating the Manifest</a></li>
-      <li><a href="#code">Implementing the Code</a>
-      </li>
+      <li><a href="#manifest">Creating the manifest</a></li>
+      <li><a href="#code">Implementing the code</a></li>
     </ol>
+  </li>
+
+ </ol>
+
+    <h2>Key classes</h2>
+    <ol>
+      <li>{@link android.app.admin.DeviceAdminReceiver}</li>
+      <li>{@link android.app.admin.DevicePolicyManager}</li>
+      <li>{@link android.app.admin.DeviceAdminInfo}</li>
     </ol>
-  </div>
+</div>
 </div>
 
 <p>Android 2.2 introduces support for enterprise applications by offering the
@@ -37,7 +46,10 @@
 solutions for Android-powered devices. It discusses the various features
 provided by the Device Administration API to provide stronger security for
 employee devices that are powered by Android.</p>
+
+
 <h2 id="overview">Device Administration API Overview</h2>
+
 <p>Here are examples of the types of applications that might use the Device Administration API:</p>
 <ul>
   <li>Email clients.</li>
@@ -45,14 +57,14 @@
   <li>Device management services and applications.</li>
 </ul>
 
-<h3 id="how">How Does it Work?</h3>
+<h3 id="how">How does it work?</h3>
 <p>You use the Device Administration API to write device admin applications that users
 install on their devices. The device admin application enforces the desired
 policies. Here's how it works:</p> <ul>
   <li>A system administrator writes a device admin application that enforces
 remote/local device security policies. These policies could be hard-coded into
 the app, or the application could dynamically fetch policies from a third-party
-server. </li> 
+server. </li>
 <li>The  application is installed on users' devices. Android does
 not currently have an automated provisioning solution. Some of the ways a sysadmin might
 distribute the application to users are as follows:
@@ -68,7 +80,7 @@
   <li>The system prompts the user to enable the device admin application. How
 and when this happens depends on how the application is implemented.</li>
 <li>Once  users enable the device admin application, they are subject to
-its policies. Complying with those policies typically confers benefits, such as 
+its policies. Complying with those policies typically confers benefits, such as
 access to sensitive systems and data.</li>
 </ul>
 <p>If users do not enable the device admin app, it remains on the device, but in an inactive state. Users will not be subject to its policies, and they will conversely not get any of the application's benefits&mdash;for example, they may not be able to sync data.</p>
@@ -88,12 +100,14 @@
 <p>To uninstall an existing device admin application, users need to
 first unregister the application as an administrator. </p>
 
-<h3 id ="policies">Policies</h3>
+
+<h3 id="policies">Policies</h3>
+
 <p>In an enterprise setting, it's often the case that employee devices must
 adhere to a strict set of policies that govern the use of the device. The
 Device Administration API supports the  policies listed in Table 1.
 Note that the Device Administration API currently only supports passwords for screen
-lock:</p> 
+lock:</p>
 <p class="table-caption"><strong>Table 1.</strong> Policies supported by the Device Administration API.</p>
 <table border="1">
   <tr>
@@ -109,7 +123,7 @@
     <td>Set the required number of characters for the password. For example, you
 can require PIN or passwords to have at least six characters. </td> </tr>
   <tr>
-    <td>Alphanumeric password required</td> 
+    <td>Alphanumeric password required</td>
     <td>Requires that passwords have a
 combination of letters and numbers. They may include symbolic characters.
     </td>
@@ -128,7 +142,9 @@
 need to enter their PIN or passwords again before they can use their devices and
 access data.  The value can be between 1 and 60 minutes.</td> </tr>
 </table>
-<h4>Other Features</h4>
+
+<h4>Other features</h4>
+
 <p>In addition to supporting the policies listed in the above table, the Device
 Administration API lets you do the following:</p> <ul>
   <li>Prompt user to set a new password.</li>
@@ -138,6 +154,7 @@
 
 
 <h2 id="sample">Sample Application</h2>
+
 <p>The examples used in this document are based on the <a
 href="{@docRoot}resources/samples/ApiDemos/src/com/example/
 android/apis/app/DeviceAdminSample.html">Device Administration API
@@ -147,7 +164,7 @@
 Getting the Samples</a>. Here is the  <a
 href="{@docRoot}resources/samples/ApiDemos/src/com/example/
 android/apis/app/DeviceAdminSample.html">complete code</a> for
-the sample. </p> 
+the sample. </p>
 <p>The
 sample application offers a demo of device admin features. It presents users
 with a user interface that lets them enable the device admin application. Once
@@ -169,13 +186,17 @@
 <img src="{@docRoot}images/admin/device-admin-app.png"/>
 <p class="img-caption"><strong>Figure 1.</strong> Screenshot of the Sample Application</p>
 
+
+
 <h2 id="developing">Developing a Device Administration Application</h2>
 
 <p>System administrators can use the Device Administration API to write an application
 that enforces remote/local device security policy enforcement. This section
 summarizes the steps involved in creating a device administration
 application.</p>
-<h3 id="manifest">Creating the Manifest</h3>
+
+<h3 id="manifest">Creating the manifest</h3>
+
 <p>To use the Device Administration API, the application's
 manifest must include the following:</p>
 <ul>
@@ -207,7 +228,7 @@
         &lt;action android:name=&quot;android.app.action.DEVICE_ADMIN_ENABLED&quot; /&gt;
     &lt;/intent-filter&gt;
 &lt;/receiver&gt;</pre>
- 
+
  <p>Note that:</p>
 <ul>
   <li>The activity in the sample application is an {@link android.app.Activity}
@@ -218,7 +239,7 @@
 an inner class; it just is in this example.</li>
 
 <li>The following attributes refer to string resources that for the sample application reside in
-<code>ApiDemos/res/values/strings.xml</code>. For more information about resources, see 
+<code>ApiDemos/res/values/strings.xml</code>. For more information about resources, see
 <a
 href="{@docRoot}guide/topics/resources/index.html">Application Resources</a>.
 <ul>
@@ -234,8 +255,8 @@
 a label.</li>
 </ul>
 
- 
-<li><code>android:permission=&quot;android.permission.BIND_DEVICE_ADMIN&quot; 
+
+<li><code>android:permission=&quot;android.permission.BIND_DEVICE_ADMIN&quot;
 </code> is a permission that a {@link android.app.admin.DeviceAdminReceiver} subclass must
 have, to ensure that only the system can interact with the receiver (no application can be granted this permission). This
 prevents other applications from abusing your device admin app.</li>
@@ -273,11 +294,15 @@
 include all of the policies, just the ones that are relevant for your app.
 </p>
 For more discussion of the manifest file, see the <a
-href="{@docRoot}guide/topics/manifest/manifest-intro.html">Android Developers Guide</a>. 
-<h2 id="code">Implementing the Code</h2>
+href="{@docRoot}guide/topics/manifest/manifest-intro.html">Android Developers Guide</a>.
+
+
+
+<h3 id="code">Implementing the code</h3>
+
 <p>The Device Administration API includes the following classes:</p>
 <dl>
-  <dt>{@link android.app.admin.DeviceAdminReceiver}</dt> 
+  <dt>{@link android.app.admin.DeviceAdminReceiver}</dt>
      <dd>Base class for implementing a device administration component. This class provides
 a convenience for interpreting the raw intent actions   that are sent by the
 system. Your Device Administration application must include a
@@ -287,7 +312,7 @@
 this class must have published a {@link android.app.admin.DeviceAdminReceiver} that the user
 has currently enabled. The {@link android.app.admin.DevicePolicyManager} manages policies for
 one or more {@link android.app.admin.DeviceAdminReceiver} instances</dd>
-  <dt>{@link android.app.admin.DeviceAdminInfo}</dt> 
+  <dt>{@link android.app.admin.DeviceAdminInfo}</dt>
 <dd>This class is used to specify metadata
 for a device administrator component.</dd>
 </dl>
@@ -295,6 +320,7 @@
 The rest of this section describes how you use the {@link
 android.app.admin.DeviceAdminReceiver} and
 {@link android.app.admin.DevicePolicyManager} APIs to write a device admin application.</p>
+
 <h4 id="receiver">Subclassing DeviceAdminReceiver</h4>
 <p>To create a device admin application, you must subclass
 {@link android.app.admin.DeviceAdminReceiver}. The {@link android.app.admin.DeviceAdminReceiver} class
@@ -305,7 +331,7 @@
 events. For example:</p>
 <pre>public class DeviceAdminSample extends DeviceAdminReceiver {
 
-... 
+...
     &#64;Override
     public void onEnabled(Context context, Intent intent) {
         showToast(context, &quot;Sample Device Admin: enabled&quot;);
@@ -331,30 +357,32 @@
     }
 ...
 }</pre>
-<h4 id="enabling">Enabling the Application</h4>
+
+<h4 id="enabling">Enabling the application</h4>
 <p>One of the major events a device admin application has to handle is the user
 enabling the application. The user must explicitly enable the application for
 the policies to be enforced. If the user chooses not to enable the application
 it will still be present on the device, but its policies will not be enforced, and the user will not
 get any of the application's benefits.</p>
 <p>The process of enabling the application begins when the user performs an
-action that triggers the {@link android.app.admin.DevicePolicyManager#ACTION_ADD_DEVICE_ADMIN} 
+action that triggers the {@link android.app.admin.DevicePolicyManager#ACTION_ADD_DEVICE_ADMIN}
 intent. In the
 sample application, this happens when the user clicks the <strong>Enable
 Admin</strong> button. </p>
 <p>When the user clicks the <strong>Enable Admin</strong> button, the display
-changes to prompt the user to enable the device admin application, as shown in <strong>Figure 2</strong>.</p>
+changes to prompt the user to enable the device admin application, as shown in figure
+2.</p>
 
 <img src="{@docRoot}images/admin/device-admin-activate-prompt.png"/>
 <p class="img-caption"><strong>Figure 2.</strong> Sample Application: Activating the Application</p>
 <p>Below  is the code that gets executed when the user clicks the <strong>Enable
-Admin</strong> button shown in <strong>Figure 1</strong>. </p>
+Admin</strong> button shown in figure 1. </p>
 
 <pre> private OnClickListener mEnableListener = new OnClickListener() {
     public void onClick(View v) {
         // Launch the activity to have the user enable our admin.
         Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
-        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, 
+        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
                mDeviceAdminSample);
         intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
                &quot;Additional text explaining why this needs to be added.&quot;);
@@ -379,10 +407,10 @@
 }</pre>
 
 <p>The line
-<code>intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, 
+<code>intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
 mDeviceAdminSample)</code> states that <code>mDeviceAdminSample</code> (which is
 a {@link android.app.admin.DeviceAdminReceiver} component) is the target policy.
-This line invokes the user interface shown in <strong>Figure 2</strong>, which guides users through
+This line invokes the user interface shown in figure 2, which guides users through
 adding the device administrator to the system (or allows them to reject it).</p>
 
 <p>When the application needs to perform an operation that is contingent on the
@@ -402,14 +430,16 @@
     // do something else
 }
 </pre>
-<h3 id="admin_ops">Managing Policies</h3>
+
+<h3 id="admin_ops">Managing policies</h3>
 <p>{@link android.app.admin.DevicePolicyManager} is a public class for managing policies
 enforced on a device. {@link android.app.admin.DevicePolicyManager} manages policies for one
 or more {@link android.app.admin.DeviceAdminReceiver} instances. </p>
 <p>You get a handle to the {@link android.app.admin.DevicePolicyManager} as follows: </p>
-<pre>DevicePolicyManager mDPM =
-(DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);<br
-/></pre>
+<pre>
+DevicePolicyManager mDPM =
+    (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
+</pre>
 <p>This section describes how to use {@link android.app.admin.DevicePolicyManager} to perform
  administrative tasks:</p>
 <ul>
@@ -417,26 +447,29 @@
   <li><a href="#lock">Set  device lock</a></li>
   <li><a href="#wipe">Perform data wipe</a></li>
 </ul>
+
 <h4 id="pwd">Set password policies</h4>
 <p>{@link android.app.admin.DevicePolicyManager} includes APIs for setting and enforcing the
 device password policy. In the Device Administration API, the password only applies to
 screen lock. This section describes common password-related tasks.</p>
+
 <h5>Set a password for the device</h5>
 <p>This code displays a user interface prompting the user to set a password:</p>
 <pre>Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
 startActivity(intent);
 </pre>
+
 <h5>Set the password quality</h5>
 <p>The password quality can be one of the following {@link android.app.admin.DevicePolicyManager} constants: </p>
 <dl>
-  <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_ALPHABETIC}</dt><dd>The user must enter a  
+  <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_ALPHABETIC}</dt><dd>The user must enter a
 password containing at least alphabetic (or other symbol) characters.</dd>
-  <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_ALPHANUMERIC}</dt><dd>The user must enter a  
+  <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_ALPHANUMERIC}</dt><dd>The user must enter a
 password containing at least <em>both</em> numeric <em>and</em> alphabetic (or
 other symbol) characters.</dd>
   <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_NUMERIC}</dt><dd>The user must enter a   password
 containing at least numeric characters.</dd>
-  <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_SOMETHING}</dt><dd>The policy requires some kind  
+  <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_SOMETHING}</dt><dd>The policy requires some kind
 of password, but doesn't care what it is.</dd>
   <dt>{@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}</dt><dd>
   The policy has no requirements   for the password. </dd>
@@ -448,6 +481,7 @@
 ...
 mDPM.setPasswordQuality(mDeviceAdminSample, DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
 </pre>
+
 <h5>Set the minimum password length</h5>
 <p>You can specify that a password must be at least the specified minimum
 length. For example:</p>
@@ -457,19 +491,21 @@
 ...
 mDPM.setPasswordMinimumLength(mDeviceAdminSample, pwLength);
 </pre>
+
 <h5>Set maximum failed password attempts</h5>
 <p>You can set the maximum number of allowed failed password attempts before the
 device is wiped (that is, reset to factory settings). For example:</p>
-<pre>DevicePolicyManager mDPM; 
-ComponentName mDeviceAdminSample; 
+<pre>DevicePolicyManager mDPM;
+ComponentName mDeviceAdminSample;
 int maxFailedPw;
  ...
 mDPM.setMaximumFailedPasswordsForWipe(mDeviceAdminSample, maxFailedPw);</pre>
+
 <h4 id="lock">Set  device lock</h4>
 <p>You can set the maximum period of user inactivity that can occur before the
 device locks. For example:</p>
 <pre>
-DevicePolicyManager mDPM; 
+DevicePolicyManager mDPM;
 ComponentName mDeviceAdminSample;
 ...
 long timeMs = 1000L*Long.parseLong(mTimeout.getText().toString());
@@ -477,9 +513,11 @@
 </pre>
 <p>You can also programmatically tell the device to lock immediately:</p>
 <pre>
-DevicePolicyManager mDPM; 
+DevicePolicyManager mDPM;
 mDPM.lockNow();</pre>
+
 <h4 id="wipe">Perform data wipe</h4>
+
 <p>You can use the {@link android.app.admin.DevicePolicyManager} method
 {@link android.app.admin.DevicePolicyManager#wipeData wipeData()} to reset the device to factory settings. This is useful
 if the device is lost or stolen. Often the decision to wipe the device is the
@@ -488,7 +526,7 @@
 wiped after a specific number of failed password attempts.</p>
 <p>You wipe data as follows:</p>
 <pre>
-DevicePolicyManager mDPM; 
+DevicePolicyManager mDPM;
 mDPM.wipeData(0);</pre>
 <p>The {@link android.app.admin.DevicePolicyManager#wipeData wipeData()} method takes as its parameter a bit mask of
 additional options. Currently the value must be 0. </p>
diff --git a/docs/html/guide/webapps/best-practices.jd b/docs/html/guide/webapps/best-practices.jd
new file mode 100644
index 0000000..1bde5bf
--- /dev/null
+++ b/docs/html/guide/webapps/best-practices.jd
@@ -0,0 +1,90 @@
+page.title=Best Practices for Web Apps
+@jd:body
+
+<style>
+.bold li {
+  font-weight:bold;
+}
+.bold li * {
+  font-weight:normal;
+}
+</style>
+
+<p>Developing web pages and web applications for mobile devices presents a different set of
+challenges compared to developing a web page for the typical
+desktop web browser. To help you get started, the following is a list of practices you should
+follow in order to provide the most effective web application for Android and other mobile
+devices.</p>
+
+<ol class="bold">
+
+<li>Redirect mobile devices to a dedicated mobile version of your web site
+  <p>There are several ways you can redirect requests to the mobile version of your web site, using
+server-side redirects. Most often, this is done by "sniffing" the User Agent
+string provided by the web browser. To determine whether to serve a mobile version of your site, you
+should simply look for the "mobile" string in the User Agent, which matches a wide variety of mobile
+devices. If necessary, you can also identify the specific operating system in the User Agent string
+(such as "Android 2.1").</p>
+</li>
+
+
+<li>Use a valid markup DOCTYPE that's appropriate for mobile devices
+  <p>The most common markup language used for mobile web sites is <a
+href="http://www.w3.org/TR/2008/REC-xhtml-basic-20080729/">XHTML Basic</a>. This standard
+ensures specific markup for your web site that works best on mobile devices. For instance, it does
+not allow HTML frames or nested tables, which perform poorly on mobile devices. Along with the
+DOCTYPE, be sure to declare the appropriate character encoding for the document (such as
+UTF-8).</p>
+  <p>For example:</p>
+<pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
+    "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd"&gt;
+</pre>
+
+  <p>Also be sure that your web page markup is valid against the declared DOCTYPE. Use a
+validator, such as the one available at
+<a href="http://validator.w3.org/">http://validator.w3.org</a>.</p>
+</li>
+
+
+<li>Use viewport meta data to properly resize your web page
+  <p>In your document {@code &lt;head&gt;}, you should provide meta data that specifies how you
+want the browser's viewport to render your web page. For example, your viewport meta data can
+specify the height and width for the browser's viewport, the initial web page scale and even the
+target screen density.</p>
+  <p>For example:</p>
+<pre>
+&lt;meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"&gt;
+</pre>
+  <p>For more information about how to use viewport meta data for Android-powered devices, read <a
+href="{@docRoot}guide/webapps/targetting.html">Targetting Android Devices</a>.</p>
+</li>
+
+
+<li>Avoid multiple file requests
+  <p>Because mobile devices typically have a connection speed far slower than a desktop
+computer, you should make your web pages load as fast as possible. One way to speed it up is to
+avoid loading extra files such as stylesheets and script files in the {@code
+&lt;head&gt;}. Instead, provide your CSS and JavaScript directly in the &lt;head&gt; (or
+at the end of the &lt;body&gt;, for scripts that you don't need until the page is loaded).
+Alternatively, you should optimize the size and speed of your files by compressing them with tools
+like <a href="http://code.google.com/p/minify/">Minify</a>.</p>
+</li>
+
+
+<li>Use a vertical linear layout
+  <p>Avoid the need for the user to scroll left and right while navigating your web
+page. Scrolling up and down is easier for the user and makes your web page simpler.</p>
+</li>
+
+</ol>
+
+<p>For a more thorough guide to creating great mobile web applications, see the W3C's <a
+href="http://www.w3.org/TR/mobile-bp/">Mobile Web Best Practices</a>. For other guidance on
+improving the speed of your web site (for mobile and desktop), see Yahoo!'s guide to <a
+href="http://developer.yahoo.com/performance/index.html#rules">Exceptional Performance</a> and
+Google's speed tutorials in <a href="http://code.google.com/speed/articles/">Let's make the web
+faster</a>.</p>
+
+
diff --git a/docs/html/guide/webapps/debugging.jd b/docs/html/guide/webapps/debugging.jd
new file mode 100644
index 0000000..098e17c
--- /dev/null
+++ b/docs/html/guide/webapps/debugging.jd
@@ -0,0 +1,158 @@
+page.title=Debugging Web Apps
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+  <li>You can debug your web app using console methods in JavaScript</li>
+  <li>If debugging in a custom WebView, you need to implement a callback method to handle debug
+messages</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+  <li><a href="#Browser">Using Console APIs in the Android Browser</a></li>
+  <li><a href="#WebView">Using Console APIs in WebView</a></li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+  <li><a href="{@docRoot}guide/developing/debug-tasks.html">Debugging Tasks</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>If you're developing a web application for Android, you can debug your JavaScript
+using the {@code console} JavaScript APIs, which output messages to logcat. If you're familiar with
+debugging web pages with Firebug or Web Inspector, then you're probably familiar
+with using {@code console} (such as {@code console.log()}). Android's WebKit framework supports most
+of the same APIs, so you can receive logs from your web page when debugging in Android's Browser
+or in your own {@link android.webkit.WebView}.</p>
+
+
+
+<h2 id="Browser">Using Console APIs in the Android Browser</h2>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+  <h2>Logcat</h2>
+  <p>Logcat is a tool that dumps a log of system messages. The messages include a stack trace when
+the device throws an error, as well as log messages written from your application and
+those written using JavaScript {@code console} APIs.</p>
+  <p>To run logcat and view messages, execute
+{@code adb logcat} from your Android SDK {@code tools/} directory, or, from DDMS, select
+<strong>Device > Run logcat</strong>. When using the <a href="{@docRoot}sdk/eclipse-adt.html">ADT
+plugin for Eclipse</a>, you can also view logcat messages by opening the Logcat view, available from
+<strong>Window > Show View > Other > Android > Logcat</strong>.</p>
+  <p>See <a href="{@docRoot}guide/developing/debug-tasks.html">Debugging
+Tasks</a> for more information about logcat.</p>
+</div>
+</div>
+
+<p>When you call a {@code console} function (in the DOM's {@code window.console} object),
+the output appears in logcat. For example, if your web page executes the following
+JavaScript:</p>
+<pre>
+console.log("Hello World");
+</pre>
+<p>Then the logcat message looks something like this:</p>
+<pre class="no-pretty-print">
+Console: Hello World http://www.example.com/hello.html :82
+</pre>
+
+<p>The format of the message might appear different depending on which version of Android you're
+using. On Android 2.1 and higher, console messages from the Android Browser
+are tagged with the name "browser". On Android 1.6 and lower, Android Browser
+messages are tagged with the name "WebCore".</p>
+
+<p>Android's WebKit does not implement all of the console APIs available in other desktop browsers.
+You can, however, use the basic text logging functions:</p>
+<ul>
+  <li>{@code console.log(String)}</li>
+  <li>{@code console.info(String)}</li>
+  <li>{@code console.warn(String)}</li>
+  <li>{@code console.error(String)}</li>
+</ul>
+
+<p>Other console functions don't raise errors, but might not behave the same as what you
+expect from other web browsers.</p>
+
+
+
+<h2 id="WebView">Using Console APIs in WebView</h2>
+
+<p>If you've implemented a custom {@link android.webkit.WebView} in your application, all the
+same console APIs are supported when debugging your web page in WebView. On Android
+1.6 and lower, console messages are automatically sent to logcat with the
+"WebCore" logging tag. If you're targetting Android 2.1 (API Level 7) or higher, then you must
+provide a {@link android.webkit.WebChromeClient}
+that implements the {@link android.webkit.WebChromeClient#onConsoleMessage(String,int,String)
+onConsoleMessage()} callback method, in order for console messages to appear in logcat.</p>
+
+<p>Additionally, the {@link
+android.webkit.WebChromeClient#onConsoleMessage(String,int,String)} method introduced in API
+Level 7 has been deprecated in favor of {@link
+android.webkit.WebChromeClient#onConsoleMessage(ConsoleMessage)} in API Level 8.</p>
+
+<p>Whether you're developing for Android 2.1 (API Level 7) or Android 2.2 (API Level 8 or
+greater), you must implement {@link android.webkit.WebChromeClient} and override the appropriate
+{@link
+android.webkit.WebChromeClient#onConsoleMessage(String,int,String) onConsoleMessage()} callback
+method. Then, apply the {@link android.webkit.WebChromeClient} to your {@link
+android.webkit.WebView} with {@link android.webkit.WebView#setWebChromeClient(WebChromeClient)
+setWebChromeClient()}.
+
+<p>Using API Level 7, this is how your code for {@link
+android.webkit.WebChromeClient#onConsoleMessage(String,int,String)} might look:</p>
+
+<pre>
+WebView myWebView = (WebView) findViewById(R.id.webview);
+myWebView.setWebChromeClient(new WebChromeClient() {
+  public void onConsoleMessage(String message, int lineNumber, String sourceID) {
+    Log.d("MyApplication", message + " -- From line "
+                         + lineNumber + " of "
+                         + sourceID);
+  }
+});
+</pre>
+
+<p>With API Level 8 or greater, your code for {@link
+android.webkit.WebChromeClient#onConsoleMessage(ConsoleMessage)} might look like this:</p>
+
+<pre>
+WebView myWebView = (WebView) findViewById(R.id.webview);
+myWebView.setWebChromeClient(new WebChromeClient() {
+  public boolean onConsoleMessage(ConsoleMessage cm) {
+    Log.d("MyApplication", cm.{@link android.webkit.ConsoleMessage#message()} + " -- From line "
+                         + cm.{@link android.webkit.ConsoleMessage#lineNumber()} + " of "
+                         + cm.{@link android.webkit.ConsoleMessage#sourceId()} );
+    return true;
+  }
+});
+</pre>
+
+<p>The {@link android.webkit.ConsoleMessage} also includes a {@link
+android.webkit.ConsoleMessage.MessageLevel MessageLevel} to indicate the type of console message
+being delivered. You can query the message level with {@link
+android.webkit.ConsoleMessage#messageLevel()} to determine the severity of the message, then
+use the appropriate {@link android.util.Log} method or take other appropriate actions.</p>
+
+<p>Whether you're using {@link
+android.webkit.WebChromeClient#onConsoleMessage(String,int,String)} or {@link
+android.webkit.WebChromeClient#onConsoleMessage(ConsoleMessage)}, when you execute a console method
+in your web page, Android calls the appropriate {@link
+android.webkit.WebChromeClient#onConsoleMessage(String,int,String)
+onConsoleMessage()} method so you can report the error. For example, with the example code above,
+a logcat message is printed that looks like this:</p>
+
+<pre class="no-pretty-print">
+Hello World -- From line 82 of http://www.example.com/hello.html
+</pre>
+
+
+
+
+
+
diff --git a/docs/html/guide/webapps/targetting.jd b/docs/html/guide/webapps/targetting.jd
new file mode 100644
index 0000000..844b9ca
--- /dev/null
+++ b/docs/html/guide/webapps/targetting.jd
@@ -0,0 +1,419 @@
+page.title=Targetting Android Devices
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+  <li>You can target your web page for different screens using viewport metadata, CSS, and
+JavaScript</li>
+  <li>Techniques in this document work for Android 2.0 and greater</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+<li><a href="#Metadata">Using Viewport Metadata</a>
+  <ol>
+    <li><a href="#ViewportSize">Defining the viewport size</a></li>
+    <li><a href="#ViewportScale">Defining the viewport scale</a></li>
+    <li><a href="#ViewportDensity">Defining the viewport target density</a></li>
+  </ol>
+</li>
+<li><a href="#DensityCSS">Targetting Device Density with CSS</a></li>
+<li><a href="#DensityJS">Targetting Device Density with JavaScript</a></li>
+</ol>
+
+</div>
+</div>
+
+
+<p>If you're developing a web application for Android or redesigning one for mobile devices, you
+should account for some factors that affect the way the Android Browser renders your web page by
+default. There are two fundamental factors that you should account for:</p>
+
+<dl>
+  <dt>The size of the viewport and scale of the web page</dt>
+    <dd>When the Android Browser loads a web page, the default behavior is to load the
+page in "overview mode," which provides a zoomed-out perspective of the web page. You can override
+this behavior for your web page by defining the default dimensions of the viewport or the initial
+scale of the viewport. You can also control how much the user can zoom in and out of your web
+page, if at all.
+    <p>However, the user can also disable overview mode in the
+Browser settings, so you should not assume that your page will load in overview mode. You
+should instead customize the viewport size and/or scale as appropriate for your page.</p></dd>
+
+  <dt>The device's screen density</dt>
+    <dd>The screen density (the number of pixels per inch) on an Android-powered device affects
+the resolution and size at which a web page is displayed. (There are three screen density
+categories: low, medium, and high.) The Android Browser compensates for variations in the screen
+density by scaling a web page so that all devices display the web page at the same perceivable size
+as a medium-density screen. If graphics are an important element of your web design, you
+should pay close attention to the scaling that occurs on different densities, because image scaling
+can produce artifacts (blurring and pixelation). 
+      <p>To provide the best visual representation on all
+screen densities, you should control how scaling occurs by providing viewport metadata about
+your web page's target screen density and providing alternative graphics for different screen
+densities, which you can apply to different screens using CSS or JavaScript.</p></dd>
+</dl>
+
+<p>The rest of this document describes how you can account for these effects, and how to target
+your web page for specific screen configurations.</p>
+
+<p class="note"><strong>Note:</strong> The features described in this document are supported
+by the Android Browser application on Android 2.0 and greater. Third-party web browsers running on
+Android might not support these techniques for controlling the viewport size and targetting
+screen densities.</p>
+
+
+
+<h2 id="Metadata">Using Viewport Metadata</h2>
+
+<p>The viewport is the area in which the Android Browser
+draws a web page. Although the viewport's visible area matches the size of the screen,
+the viewport has its own dimensions that determine the number of pixels available to a web page.
+That is, the number of pixels available to a web page before it exceeds the screen area is
+defined by the dimensions of the viewport,
+not the dimensions of the device screen. For example, although a device screen might have a width of
+480 pixels, the viewport can have a width of 800 pixels, so that a web page designed to be 800
+pixels wide is completely visible on the screen.</p>
+
+<p>You can define properties of the viewport for your web page using the {@code "viewport"}
+property in an HTML {@code &lt;meta&gt;} tag (which must
+be placed in your document {@code &lt;head&gt;}). You can define multiple viewport properties in the
+{@code &lt;meta&gt;} tag's {@code content} attribute. For example, you can define the height and
+width of the viewport, the initial scale of the page, and the target screen density.
+Each viewport property in the {@code content} attribute must be separated by a comma.</p>
+
+<p>For example, the following snippet from an HTML document specifies that the viewport width
+should exactly match the device screen width and that the ability to zoom should be disabled:</p>
+
+<pre>
+&lt;head&gt;
+    &lt;title&gt;Example&lt;/title&gt;
+    &lt;meta name="viewport" content="width=device-width, user-scalable=no" /&gt;
+&lt;/head&gt;
+</pre>
+
+<p>That's an example of just two viewport properties. The following syntax shows all of the
+supported viewport properties and the general types of values accepted by each one:</p>
+
+<pre>
+&lt;meta name="viewport"
+      content="
+          <b>height</b> = [<em>pixel_value</em> | device-height] ,
+          <b>width</b> = [<em>pixel_value</em> | device-width ] ,
+          <b>initial-scale</b> = <em>float_value</em> ,
+          <b>minimum-scale</b> = <em>float_value</em> ,
+          <b>maximum-scale</b> = <em>float_value</em> ,
+          <b>user-scalable</b> = [yes | no] ,
+          <b>target-densitydpi</b> = [<em>dpi_value</em> | device-dpi |
+                               high-dpi | medium-dpi | low-dpi]
+          " /&gt;
+</pre>
+
+<p>The following sections discuss how to use each of these viewport properties and exactly what the
+accepted values are.</p>
+
+<div class="figure" style="width:300px">
+  <img src="{@docRoot}images/webapps/compare-default.png" alt="" height="300" />
+  <p class="img-caption"><strong>Figure 1.</strong> A web page with no viewport metadata and an
+image that's 320 pixels wide (the viewport is 800 pixels wide, by default).</p>
+</div>
+
+
+<div class="figure" style="width:300px">
+  <img src="{@docRoot}images/webapps/compare-width400.png" alt="" height="300" />
+  <p class="img-caption"><strong>Figure 2.</strong> A web page with viewport {@code width=400}
+(the image in the web page is 320 pixels wide).</p>
+</div>
+
+
+<h3 id="ViewportSize">Defining the viewport size</h3>
+
+<p>Viewport's {@code height} and {@code width} properties allow you to specify the size of the
+viewport (the number of pixels available to the web page before it goes off screen). By default, the
+Android Browser's minimum viewport width is 800 pixels, so if your web
+page specifies its size to be 320 pixels wide, then your page renders smaller than the visible
+screen (even if the physical screen is 320 pixels wide, because the viewport simulates a
+drawable area that's 800 pixels wide), as shown in figure 1. So, you should explicitly define the
+viewport {@code width} to match the width for which you have designed your web page.</p>
+
+<p class="note"><strong>Note:</strong> Width values that are greater than 10,000 are ignored and
+values less than (or equal to) 320 result in a value equal to the device-width. Height values that
+are greater then 10,000 or less than 200 are also ignored.</p>
+
+<p>For example, if your web page is designed to be exactly 320 pixels wide, then you might
+want to specify that for the viewport width:</p>
+
+<pre>
+&lt;meta name="viewport" content="width=320" /&gt;
+</pre>
+
+<p>In this case, your web page exactly fits the screen width, because the web page width and
+viewport width are the same.</p>
+
+<p>To demonstrate how this property affects the size of
+your web page, figure 2 shows a web page that contains an image that's 320 pixels wide, but with the
+viewport width set to 400.</p>
+
+
+<p class="note"><strong>Note:</strong> If you set the viewport width to match your web page width
+and the device screen width does <em>not</em> match those dimensions, then the web page
+still fits the screen even if the device has a high or low-density screen, because the
+Android Browser scales web pages to match the perceived size on a medium-density
+screen, by default (as you can see in figure 2, when comparing the hdpi device to the mdpi device).
+Screen densities are discussed more in <a href="#ViewportDensity">Defining the viewport target
+density</a>.</p>
+
+
+<h4>Automatic sizing</h4>
+
+<p>As an alternative to specifying the viewport dimensions with exact pixels, you can set the
+viewport size to always match the dimensions of the device screen, by defining the
+viewport properties {@code height}
+and {@code width} with the values {@code device-height} and {@code device-width}, respectively. This
+is appropriate when you're developing a web application that has a fluid width (not fixed width),
+but you want it to appear as if it's fixed (to perfectly fit every screen as
+if the web page width is set to match each screen). For example:</p>
+
+<pre>
+&lt;meta name="viewport" content="width=device-width" /&gt;
+</pre>
+
+<p>This results in the viewport width matching whatever the current screen width is, as shown in
+figure 3. It's important to notice that, this results in images being scaled to fit the screen
+when the current device does not match the <a href="#ViewportDensity">target
+density</a>, which is medium-density if you don't specify otherwise. As a result, the image
+displayed on the high-density device in figure 3 is scaled up in order to match the width
+of a screen with a medium-density screen.</p>
+
+<div class="figure" style="width:300px">
+  <img src="{@docRoot}images/webapps/compare-initialscale.png" alt="" height="300" />
+  <p class="img-caption"><strong>Figure 3.</strong> A web page with viewport {@code
+width=device-width} <em>or</em> {@code initial-scale=1.0}.</p>
+</div>
+
+<p class="note"><strong>Note:</strong> If you instead want {@code
+device-width} and {@code device-height} to match the physical screen pixels for every device,
+instead of scaling your web page to match the target density, then you must also include
+the {@code target-densitydpi} property with a value of {@code device-dpi}. This is discussed more in
+the section about <a href="#ViewportDensity">Defining the viewport density</a>. Otherwise, simply
+using {@code device-height} and {@code device-width} to define the viewport size makes your web page
+fit every device screen, but scaling occurs on your images in order to adjust for different screen
+densities.</p>
+
+
+
+<h3 id="ViewportScale">Defining the viewport scale</h3>
+
+<p>The scale of the viewport defines the level of zoom applied to the web page. Viewport
+properties allow you to specify the scale of your web page in the following ways:</p>
+<dl>
+  <dt>{@code initial-scale}</dt>
+  <dd>The initial scale of the page. The value is a float that indicates a multiplier for your web
+page size, relative to the screen size. For example, if you set the initial scale to "1.0" then the
+web page is displayed to match the resolution of the <a href="#ViewportDensity">target
+density</a> 1-to-1. If set to "2.0", then the page is enlarged (zoomed in) by a factor of 2.
+    <p>The default initial scale is calculated to fit the web page in the viewport size.
+Because the default viewport width is 800 pixels, if the device screen resolution is less than
+800 pixels wide, the initial scale is something less than 1.0, by default, in order to fit the
+800-pixel-wide page on the screen.</p></dd>
+
+  <dt>{@code minimum-scale}</dt>
+  <dd>The minimum scale to allow. The value is a float that indicates the minimum multiplier for
+your web page size, relative to the screen size. For example, if you set this to "1.0", then the
+page can't zoom out because the minimum size is 1-to-1 with the <a href="#ViewportDensity">target
+density</a>.</dd>
+
+  <dt>{@code maximum-scale}</dt>
+  <dd>The maximum scale to allow for the page. The value is a float that indicates the
+maximum multiplier for your web page size,
+relative to the screen size. For example, if you set this to "2.0", then the page can't
+zoom in more than 2 times the target size.</dd>
+
+  <dt>{@code user-scalable}</dt>
+  <dd>Whether the user can change the scale of the page at all (zoom in and out). Set to {@code yes}
+to allow scaling and {@code no} to disallow scaling. The default is {@code yes}. If you set
+this to {@code no}, then the {@code minimum-scale} and {@code maximum-scale} are ignored,
+because scaling is not possible.</dd>
+</dl>
+
+<p>All scale values must be within the range 0.01&ndash;10.</p>
+
+<p>For example:</p>
+
+<pre>
+&lt;meta name="viewport" content="initial-scale=1.0" /&gt;
+</pre>
+
+<p>This metadata sets the initial scale to be full sized, relative to the viewport's target
+density.</p>
+
+
+
+
+<h3 id="ViewportDensity">Defining the viewport target density</h3>
+
+<p>The density of a device's screen is based on the screen resolution. There are three screen
+density categories supported by Android: low (ldpi), medium (mdpi), and high (mdpi). A screen
+with low density has fewer available pixels per inch, whereas a screen with high density has more
+pixels per inch (compared to a medium density screen). The Android Browser targets a medium density 
+screen by default.</p>
+
+
+<div class="figure" style="width:300px">
+  <img src="{@docRoot}images/webapps/compare-initialscale-devicedpi.png" alt="" height="300" />
+  <p class="img-caption"><strong>Figure 4.</strong> A web page with viewport {@code
+width=device-width} and {@code target-densitydpi=device-dpi}.</p>
+</div>
+
+
+<p>Because the default target density is medium, when users have a device with a low or high density
+screen, the Android Browser scales web pages (effectively zooms the pages) so they display at a
+size that matches the perceived appearance on a medium density screen. Specifically, the Android
+Browser applies approximately 1.5x scaling to web pages on a high density screen
+(because its screen pixels are smaller) and approximately 0.75x scaling to pages on a low density
+screen (because its screen pixels are bigger).</p>
+
+<p>Due to this default scaling, figures 1, 2, and 3 show the example web page at the same physical
+size on both the high and medium density device (the high-density device shows the
+web page with a default scale factor that is 1.5 times larger than the actual pixel resolution, to
+match the target density). This can introduce some undesirable artifacts in your images.
+For example, although an image appears the same size on a medium and high-density device, the image
+on the high-density device appears more blurry, because the image is designed to be 320 pixels
+wide, but is drawn with 480 pixels.</p>
+
+<p>You can change the target screen density for your web page using the {@code target-densitydpi}
+viewport property. It accepts the following values:</p>
+
+<ul>
+<li><code>device-dpi</code> - Use the device's native dpi as the target dpi. Default scaling never
+occurs.</li>
+<li><code>high-dpi</code> - Use hdpi as the target dpi. Medium and low density screens scale down
+as appropriate.</li>
+<li><code>medium-dpi</code> - Use mdpi as the target dpi. High density screens scale up and low
+density screens scale down. This is the default target density.</li>
+<li><code>low-dpi</code> - Use ldpi as the target dpi. Medium and high density screens scale up
+as appropriate.</li>
+<li><em><code>&lt;value&gt;</code></em> - Specify a dpi value to use as the target dpi. Values must
+be within the range 70&ndash;400.</li>
+</ul></p>
+
+<p>For example, to prevent the Android Browser from scaling of your web page for different screen
+densities, set
+the {@code target-densitydpi} viewport property to {@code device-dpi}. When you do, the Android
+Browser does not scale the page and, instead, displays your web page to match the current screen
+density. In this case, you should also define the viewport width to match the device width, so your
+web page naturally fits the screen size. For example:</p>
+
+<pre>
+&lt;meta name="viewport" content="target-densitydpi=device-dpi, width=device-width" /&gt;
+</pre>
+
+<p>Figure 4 shows a web page using these viewport settings&mdash;the high-density device
+now displays the page smaller because its physical pixels are smaller than those on the
+medium-density device, so no scaling occurs and the 320-pixel-wide image is drawn using exactly 320
+pixels on both screens. (This is how you should define your viewport if
+you want to customize your web page based on screen density and provide different image assets for
+different densities, <a href="#DensityCSS">with CSS</a> or
+<a href="#DensityJS">with JavaScript</a>.)</p>
+
+
+<h2 id="DensityCSS">Targetting Device Density with CSS</h2>
+
+<p>The Android Browser supports a CSS media feature that allows you to create styles for specific
+screen densities&mdash;the <code>-webkit-device-pixel-ratio</code> CSS media feature. The
+value you apply to this feature should be either
+"0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium density,
+or high density screens, respectively.</p>
+
+<p>For example, you can create separate stylesheets for each density:</p>
+
+<pre>
+&lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 1.5)" href="hdpi.css" /&gt;
+&lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 1.0)" href="mdpi.css" /&gt;
+&lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 0.75)" href="ldpi.css" /&gt;
+</pre>
+
+
+<div class="figure" style="width:300px">
+  <img src="{@docRoot}images/webapps/compare-width-devicedpi-css.png" alt="" height="300" />
+  <p class="img-caption"><strong>Figure 5.</strong> A web page with CSS that's targetted to
+specific screen densities using the {@code -webkit-device-pixel-ratio} media feature. Notice
+that the hdpi device shows a different image that's applied in CSS.</p>
+</div>
+
+<p>Or, specify the different styles in one stylesheet:</p>
+
+<pre class="no-pretty-print">
+#header {
+    background:url(medium-density-image.png);
+}
+
+&#64;media screen and (-webkit-device-pixel-ratio: 1.5) {
+    // CSS for high-density screens
+    #header {
+        background:url(high-density-image.png);
+    }
+}
+
+&#64;media screen and (-webkit-device-pixel-ratio: 0.75) {
+    // CSS for low-density screens
+    #header {
+        background:url(low-density-image.png);
+    }
+}
+</pre>
+
+<p class="note"><strong>Note:</strong> The default style for {@code #header} applies the image
+designed for medium-density devices in order to support devices running a version of Android less
+than 2.0, which do not support the {@code -webkit-device-pixel-ratio} media feature.</p>
+
+<p>The types of styles you might want to adjust based on the screen density depend on how you've
+defined your viewport properties. To provide fully-customized styles that tailor your web page for
+each of the supported densities, you should set your viewport properties so the viewport width and
+density match the device. That is:</p>
+
+<pre>
+&lt;meta name="viewport" content="target-densitydpi=device-dpi, width=device-width" /&gt;
+</pre>
+
+<p>This way, the Android Browser does not perform scaling on your web page and the viewport width
+matches the screen width exactly. On its own, these viewport properties create results shown in
+figure 4. However, by adding some custom CSS using the {@code -webkit-device-pixel-ratio} media
+feature, you can apply different styles. For example, figure 5 shows a web page with these viewport
+properties and also some CSS added that applies a high-resolution image for high-density
+screens.</p>
+
+
+
+<h2 id="DensityJS">Targetting Device Density with JavaScript</h2>
+
+<p>The Android Browser supports a DOM property that allows you to query the density of the current
+device&mdash;the <code>window.devicePixelRatio</code> DOM property. The value of this property
+specifies the scaling factor used for the current device. For example, if the value
+of <code>window.devicePixelRatio</code> is "1.0", then the device is considered a medium density
+device and no scaling is applied by default; if the value is "1.5", then the device is
+considered a high density device and the page is scaled 1.5x by default; if the value
+is "0.75", then the device is considered a low density device and the page is scaled
+0.75x by default. Of course, the scaling that the Android Browser applies is based on the web page's
+target density&mdash;as described in the section about <a href="#ViewportDensity">Defining the
+viewport target density</a>, the default target is medium-density, but you can change the
+target to affect how your web page is scaled for different screen densities.</p>
+
+<p>For example, here's how you can query the device density with JavaScript:</p>
+
+<pre>
+if (window.devicePixelRatio == 1.5) {
+  alert("This is a high-density screen");
+} else if (window.devicePixelRation == 0.75) {
+  alert("This is a low-density screen");
+}
+</pre>
+
+
+
+
+
+
+
diff --git a/docs/html/guide/webapps/webview.jd b/docs/html/guide/webapps/webview.jd
new file mode 100644
index 0000000..ed28f21
--- /dev/null
+++ b/docs/html/guide/webapps/webview.jd
@@ -0,0 +1,328 @@
+page.title=Building Web Apps in WebView
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+  <li>Use {@link android.webkit.WebView} to display web pages in your Android application
+layout</li>
+  <li>You can create interfaces from your JavaScript to your client-side Android code</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+  <li><a href="#AddingWebView">Adding a WebView to Your Application</a></li>
+  <li><a href="#UsingJavaScript">Using JavaScript in WebView</a>
+    <ol>
+      <li><a href="#EnablingJavaScript">Enabling JavaScript</a></li>
+      <li><a href="#BindingJavaScript">Binding JavaScript code to Android code</a></li>
+    </ol>
+  </li>
+  <li><a href="#HandlingNavigation">Handling Page Navigation</a>
+    <ol>
+      <li><a href="#NavigatingHistory">Navigating web page history</a></li>
+    </ol>
+  </li>
+</ol>
+
+<h2>Key classes</h2>
+<ol>
+  <li>{@link android.webkit.WebView}</li>
+  <li>{@link android.webkit.WebSettings}</li>
+  <li>{@link android.webkit.WebViewClient}</li>
+</ol>
+
+<h2>Related tutorials</h2>
+<ol>
+  <li><a href="{@docRoot}resources/tutorials/views/hello-webview.html">Web View</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>If you want to deliver a web application (or just a web page) as a part of a client application,
+you can do it using {@link android.webkit.WebView}. The {@link android.webkit.WebView} class is an
+extension of Android's {@link android.view.View} class that allows you to display web pages as a
+part of your activity layout. It does <em>not</em> include any features of a fully developed web
+browser, such as navigation controls or an address bar. All that {@link android.webkit.WebView}
+does, by default, is show a web page.</p>
+
+<p>A common scenario in which using {@link android.webkit.WebView} is helpful is when you want to
+provide information in your application that you might need to update, such as an end-user agreement
+or a user guide. Within your Android application, you can create an {@link android.app.Activity}
+that contains a {@link android.webkit.WebView}, then use that to display your document that's
+hosted online.</p>
+
+<p>Another scenario in which {@link android.webkit.WebView} can help is if your application provides
+data to the user that
+always requires an Internet connection to retrieve data, such as email. In this case, you might
+find that it's easier to build a {@link android.webkit.WebView} in your Android application that
+shows a web page with all
+the user data, rather than performing a network request, then parsing the data and rendering it in
+an Android layout. Instead, you can design a web page that's tailored for Android devices
+and then implement a {@link android.webkit.WebView} in your Android application that loads the web
+page.</p>
+
+<p>This document shows you how to get started with {@link android.webkit.WebView} and how to do some
+additional things, such as handle page navigation and bind JavaScript from your web page to
+client-side code in your Android application.</p>
+
+
+
+<h2 id="AddingWebView">Adding a WebView to Your Application</h2>
+
+<p>To add a {@link android.webkit.WebView} to your Application, simply include the {@code
+&lt;WebView&gt;} element in your activity layout. For example, here's a layout file in which the
+{@link android.webkit.WebView} fills the screen:</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;WebView  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/webview"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+/&gt;
+</pre>
+
+<p>To load a web page in the {@link android.webkit.WebView}, use {@link
+android.webkit.WebView#loadUrl(String) loadUrl()}. For example:</p>
+
+<pre>
+WebView myWebView = (WebView) findViewById(R.id.webview);
+myWebView.loadUrl("http://www.example.com");
+</pre>
+
+<p>Before this will work, however, your application must have access to the Internet. To get
+Internet access, request the {@link android.Manifest.permission#INTERNET} permission in your
+manifest file. For example:</p>
+
+<pre>
+&lt;manifest ... &gt;
+    &lt;uses-permission android:name="android.permission.INTERNET" /&gt;
+    ...
+&lt;/manifest&gt;
+</pre>
+
+<p>That's all you need for a basic {@link android.webkit.WebView} that displays a web page.</p>
+
+
+
+
+<h2 id="UsingJavaScript">Using JavaScript in WebView</h2>
+
+<p>If the web page you plan to load in your {@link android.webkit.WebView} use JavaScript, you
+must enable JavaScript for your {@link android.webkit.WebView}. Once JavaScript is enabled, you can
+also create interfaces between your application code and your JavaScript code.</p>
+
+
+<h3 id="EnablingJavaScript">Enabling JavaScript</h3>
+
+<p>JavaScript is disabled in a {@link android.webkit.WebView} by default. You can enable it
+through the {@link
+android.webkit.WebSettings} attached to your {@link android.webkit.WebView}. You can retrieve {@link
+android.webkit.WebSettings} with {@link android.webkit.WebView#getSettings()}, then enable
+JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
+setJavaScriptEnabled()}.</p>
+
+<p>For example:</p>
+
+<pre>
+WebView myWebView = (WebView) findViewById(R.id.webview);
+WebSettings webSettings = myWebView.getSettings();
+webSettings.setJavaScriptEnabled(true);
+</pre>
+
+<p>{@link android.webkit.WebSettings} provides access to a variety of other settings that you might
+find useful. For example, if you're developing a web application
+that's designed specifically for the {@link android.webkit.WebView} in your Android application,
+then you can define a
+custom user agent string with {@link android.webkit.WebSettings#setUserAgentString(String)
+setUserAgentString()}, then query the custom user agent in your web page to verify that the
+client requesting your web page is actually your Android application.</p>
+
+from your Android SDK {@code tools/} directory
+<h3 id="BindingJavaScript">Binding JavaScript code to Android code</h3>
+
+<p>When developing a web application that's designed specifically for the {@link
+android.webkit.WebView} in your Android
+application, you can create interfaces between your JavaScript code and client-side Android code.
+For example, your JavaScript code can call a method in your Android code to display a {@link
+android.app.Dialog}, instead of using JavaScript's {@code alert()} function.</p>
+
+<p>To bind a new interface between your JavaScript and Android code, call {@link
+android.webkit.WebView#addJavascriptInterface(Object,String) addJavascriptInterface()}, passing it
+a class instance to bind to your JavaScript and an interface name that your JavaScript can call to
+access the class.</p>
+
+<p>For example, you can include the following class in your Android application:</p>
+
+<pre>
+public class JavaScriptInterface {
+    Context mContext;
+
+    /** Instantiate the interface and set the context */
+    JavaScriptInterface(Context c) {
+        mContext = c;
+    }
+
+    /** Show a toast from the web page */
+    public void showToast(String toast) {
+        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
+    }
+}
+</pre>
+
+<p>In this example, the {@code JavaScriptInterface} class allows the web page to create a {@link
+android.widget.Toast} message, using the {@code showToast()} method.</p>
+
+<p>You can bind this class to the JavaScript that runs in your {@link android.webkit.WebView} with
+{@link android.webkit.WebView#addJavascriptInterface(Object,String) addJavascriptInterface()} and
+name the interface {@code Android}. For example:</p>
+
+<pre>
+WebView webView = (WebView) findViewById(R.id.webview);
+webView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
+</pre>
+
+<p>This creates an interface called {@code Android} for JavaScript running in the {@link
+android.webkit.WebView}. At this point, your web application has access to the {@code
+JavaScriptInterface} class. For example, here's some HTML and JavaScript that creates a toast
+message using the new interface when the user clicks a button:</p>
+
+<pre>
+&lt;input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" /&gt;
+
+&lt;script type="text/javascript"&gt;
+    function showAndroidToast(toast) {
+        Android.showToast(toast);
+    }
+&lt;/script&gt;
+</pre>
+
+<p>There's no need to initialize the {@code Android} interface from JavaScript. The {@link
+android.webkit.WebView} automatically makes it
+available to your web page. So, at the click of the button, the {@code showAndroidToast()}
+function uses the {@code Android} interface to call the {@code JavaScriptInterface.showToast()}
+method.</p>
+
+<p class="note"><strong>Note:</strong> The object that is bound to your JavaScript runs in
+another thread and not in the thread in which it was constructed.</p>
+
+<p class="caution"><strong>Caution:</strong> Using {@link
+android.webkit.WebView#addJavascriptInterface(Object,String) addJavascriptInterface()} allows
+JavaScript to control your Android application. This can be a very useful feature or a dangerous
+security issue. When the HTML in the {@link android.webkit.WebView} is untrustworthy (for example,
+part or all of the HTML
+is provided by an unknown person or process), then an attacker can include HTML that executes
+your client-side code and possibly any code of the attacker's choosing. As such, you should not use
+{@link android.webkit.WebView#addJavascriptInterface(Object,String) addJavascriptInterface()} unless
+you wrote all of the HTML and JavaScript that appears in your {@link android.webkit.WebView}. You
+should also not allow the user to
+navigate to other web pages that are not your own, within your {@link android.webkit.WebView}
+(instead, allow the user's
+default browser application to open foreign links&mdash;by default, the user's web browser
+opens all URL links, so be careful only if you handle page navigation as described in the
+following section).</p>
+
+
+
+
+<h2 id="HandlingNavigation">Handling Page Navigation</h2>
+
+<p>When the user clicks a link from a web page in your {@link android.webkit.WebView}, the default
+behavior is
+for Android to launch an application that handles URLs. Usually, the default web browser opens and
+loads the destination URL. However, you can override this behavior for your {@link
+android.webkit.WebView},
+so links open within your {@link android.webkit.WebView}. You can then allow the user to navigate
+backward and forward through their web page history that's maintained by your {@link
+android.webkit.WebView}.</p>
+
+<p>To open links clicked by the user, simply provide a {@link
+android.webkit.WebViewClient} for your {@link android.webkit.WebView}, using {@link
+android.webkit.WebView#setWebViewClient(WebViewClient) setWebViewClient()}. For example:</p>
+
+<pre>
+WebView myWebView = (WebView) findViewById(R.id.webview);
+myWebView.{@link android.webkit.WebView#setWebViewClient(WebViewClient) setWebViewClient}(new WebViewClient());
+</pre>
+
+<p>That's it. Now all links the user clicks load in your {@link android.webkit.WebView}.</p>
+
+<p>If you want more control over where a clicked link load, create your own {@link
+android.webkit.WebViewClient} that overrides the {@link
+android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
+shouldOverrideUrlLoading()} method. For example:</p>
+
+<pre>
+private class MyWebViewClient extends WebViewClient {
+    &#64;Override
+    public boolean {@link android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String) shouldOverrideUrlLoading}(WebView view, String url) {
+        if (Uri.parse(url).getHost().equals("www.example.com")) {
+            // This is my web site, so do not override; let my WebView load the page
+            return false;
+        }
+        // Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+        startActivity(intent);
+        return true;
+    }
+}
+</pre>
+
+<p>Then create an instance of this new {@link android.webkit.WebViewClient} for the {@link
+android.webkit.WebView}:</p>
+
+<pre>
+WebView myWebView = (WebView) findViewById(R.id.webview);
+myWebView.{@link android.webkit.WebView#setWebViewClient(WebViewClient) setWebViewClient}(new MyWebViewClient());
+</pre>
+
+<p>Now when the user clicks a link, the system calls
+{@link android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
+shouldOverrideUrlLoading()}, which checks whether the URL host matches a specific domain (as defined
+above). If it does match, then the method returns false in order to <em>not</em> override the URL
+loading (it allows the {@link android.webkit.WebView} to load the URL as usual). If the URL host
+does not match, then an {@link android.content.Intent} is created to
+launch the default Activity for handling URLs (which resolves to the user's default web
+browser).</p>
+
+
+
+
+<h3 id="NavigatingHistory">Navigating web page history</h3>
+
+<p>When your {@link android.webkit.WebView} overrides URL loading, it automatically accumulates a
+history of visited web
+pages. You can navigate backward and forward through the history with {@link
+android.webkit.WebView#goBack()} and {@link android.webkit.WebView#goForward()}.</p>
+
+<p>For example, here's how your {@link android.app.Activity} can use the device BACK key to navigate
+backward:</p>
+
+<pre>
+&#64;Override
+public boolean {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown}(int keyCode, KeyEvent event) {
+    // Check if the key event was the BACK key and if there's history
+    if ((keyCode == KeyEvent.KEYCODE_BACK) &amp;&amp; myWebView.{@link android.webkit.WebView#canGoBack() canGoBack}() {
+        myWebView.{@link android.webkit.WebView#goBack() goBack}();
+        return true;
+    }
+    // If it wasn't the BACK key or there's no web page history, bubble up to the default
+    // system behavior (probably exit the activity)
+    return super.onKeyDown(keyCode, event);
+}
+</pre>
+
+<p>The {@link android.webkit.WebView#canGoBack()} method returns
+true if there is actually web page history for the user to visit. Likewise, you can use {@link
+android.webkit.WebView#canGoForward()} to check whether there is a forward history. If you don't
+perform this check, then once the user reaches the end of the history, {@link
+android.webkit.WebView#goBack()} or {@link android.webkit.WebView#goForward()} does nothing.</p>
+
+
+
+
+
+
diff --git a/docs/html/images/webapps/compare-default.png b/docs/html/images/webapps/compare-default.png
new file mode 100644
index 0000000..9495a05
--- /dev/null
+++ b/docs/html/images/webapps/compare-default.png
Binary files differ
diff --git a/docs/html/images/webapps/compare-initialscale-devicedpi.png b/docs/html/images/webapps/compare-initialscale-devicedpi.png
new file mode 100644
index 0000000..6bb758a
--- /dev/null
+++ b/docs/html/images/webapps/compare-initialscale-devicedpi.png
Binary files differ
diff --git a/docs/html/images/webapps/compare-initialscale.png b/docs/html/images/webapps/compare-initialscale.png
new file mode 100644
index 0000000..2232d5b
--- /dev/null
+++ b/docs/html/images/webapps/compare-initialscale.png
Binary files differ
diff --git a/docs/html/images/webapps/compare-width-devicedpi-css.png b/docs/html/images/webapps/compare-width-devicedpi-css.png
new file mode 100644
index 0000000..bb4ab31
--- /dev/null
+++ b/docs/html/images/webapps/compare-width-devicedpi-css.png
Binary files differ
diff --git a/docs/html/images/webapps/compare-width400.png b/docs/html/images/webapps/compare-width400.png
new file mode 100644
index 0000000..669a234
--- /dev/null
+++ b/docs/html/images/webapps/compare-width400.png
Binary files differ
diff --git a/docs/html/sitemap.txt b/docs/html/sitemap.txt
index d5be8f1..7a0b8ac 100644
--- a/docs/html/sitemap.txt
+++ b/docs/html/sitemap.txt
@@ -164,6 +164,11 @@
 http://developer.android.com/guide/practices/design/performance.html
 http://developer.android.com/guide/practices/design/responsiveness.html
 http://developer.android.com/guide/practices/design/seamlessness.html
+http://developer.android.com/guide/webapps/targetting.html
+http://developer.android.com/guide/webapps/webview.html
+http://developer.android.com/guide/webapps/debugging.html
+http://developer.android.com/guide/webapps/best-practices.html
+http://developer.android.com/guide/topics/admin/device-admin.html
 http://developer.android.com/guide/appendix/api-levels.html
 http://developer.android.com/guide/appendix/media-formats.html
 http://developer.android.com/guide/appendix/g-app-intents.html
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/MediaErrors.h b/include/media/stagefright/MediaErrors.h
index 73d0f77..e44122d 100644
--- a/include/media/stagefright/MediaErrors.h
+++ b/include/media/stagefright/MediaErrors.h
@@ -39,6 +39,7 @@
 
     // Not technically an error.
     INFO_FORMAT_CHANGED    = MEDIA_ERROR_BASE - 12,
+    INFO_DISCONTINUITY     = MEDIA_ERROR_BASE - 13,
 };
 
 }  // namespace android
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/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index ab2f11d..d2bd9f2 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -97,6 +97,8 @@
     kKeyAutoLoop          = 'autL',  // bool (int32_t)
 
     kKeyValidSamples      = 'valD',  // int32_t
+
+    kKeyIsUnreadable      = 'unre',  // bool (int32_t)
 };
 
 enum {
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 875bc5b..2bb7783 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -32,7 +32,8 @@
 struct OMXCodec : public MediaSource,
                   public MediaBufferObserver {
     enum CreationFlags {
-        kPreferSoftwareCodecs = 1,
+        kPreferSoftwareCodecs    = 1,
+        kIgnoreCodecSpecificData = 2
     };
     static sp<MediaSource> Create(
             const sp<IOMX> &omx,
@@ -103,6 +104,7 @@
         kSupportsMultipleFramesPerInputBuffer = 1024,
         kAvoidMemcopyInputRecordingFrames     = 2048,
         kRequiresLargerEncoderOutputBuffer    = 4096,
+        kOutputBuffersAreUnreadable           = 8192,
     };
 
     struct BufferInfo {
@@ -247,9 +249,10 @@
 
     void dumpPortStatus(OMX_U32 portIndex);
 
-    status_t configureCodec(const sp<MetaData> &meta);
+    status_t configureCodec(const sp<MetaData> &meta, uint32_t flags);
 
-    static uint32_t getComponentQuirks(const char *componentName);
+    static uint32_t getComponentQuirks(
+            const char *componentName, bool isEncoder);
 
     static void findMatchingCodecs(
             const char *mime,
diff --git a/include/utils/Looper.h b/include/utils/Looper.h
index 3f00b78..cc51490 100644
--- a/include/utils/Looper.h
+++ b/include/utils/Looper.h
@@ -20,9 +20,22 @@
 #include <utils/threads.h>
 #include <utils/RefBase.h>
 #include <utils/KeyedVector.h>
+#include <utils/Timers.h>
 
 #include <android/looper.h>
 
+// Currently using poll() instead of epoll_wait() since it does a better job of meeting a
+// timeout deadline.  epoll_wait() typically causes additional delays of up to 10ms
+// beyond the requested timeout.
+//#define LOOPER_USES_EPOLL
+//#define LOOPER_STATISTICS
+
+#ifdef LOOPER_USES_EPOLL
+#include <sys/epoll.h>
+#else
+#include <sys/poll.h>
+#endif
+
 /*
  * Declare a concrete type for the NDK's looper forward declaration.
  */
@@ -190,13 +203,54 @@
 
     const bool mAllowNonCallbacks; // immutable
 
-    int mEpollFd; // immutable
     int mWakeReadPipeFd;  // immutable
     int mWakeWritePipeFd; // immutable
+    Mutex mLock;
+
+#ifdef LOOPER_USES_EPOLL
+    int mEpollFd; // immutable
 
     // Locked list of file descriptor monitoring requests.
-    Mutex mLock;
-    KeyedVector<int, Request> mRequests;
+    KeyedVector<int, Request> mRequests;  // guarded by mLock
+#else
+    // The lock guards state used to track whether there is a poll() in progress and whether
+    // there are any other threads waiting in wakeAndLock().  The condition variables
+    // are used to transfer control among these threads such that all waiters are
+    // serviced before a new poll can begin.
+    // The wakeAndLock() method increments mWaiters, wakes the poll, blocks on mAwake
+    // until mPolling becomes false, then decrements mWaiters again.
+    // The poll() method blocks on mResume until mWaiters becomes 0, then sets
+    // mPolling to true, blocks until the poll completes, then resets mPolling to false
+    // and signals mResume if there are waiters.
+    bool mPolling;      // guarded by mLock
+    uint32_t mWaiters;  // guarded by mLock
+    Condition mAwake;   // guarded by mLock
+    Condition mResume;  // guarded by mLock
+
+    Vector<struct pollfd> mRequestedFds;  // must hold mLock and mPolling must be false to modify
+    Vector<Request> mRequests;            // must hold mLock and mPolling must be false to modify
+
+    ssize_t getRequestIndexLocked(int fd);
+    void wakeAndLock();
+#endif
+
+#ifdef LOOPER_STATISTICS
+    static const int SAMPLED_WAKE_CYCLES_TO_AGGREGATE = 100;
+    static const int SAMPLED_POLLS_TO_AGGREGATE = 1000;
+
+    nsecs_t mPendingWakeTime;
+    int mPendingWakeCount;
+
+    int mSampledWakeCycles;
+    int mSampledWakeCountSum;
+    nsecs_t mSampledWakeLatencySum;
+
+    int mSampledPolls;
+    int mSampledZeroPollCount;
+    int mSampledZeroPollLatencySum;
+    int mSampledTimeoutPollCount;
+    int mSampledTimeoutPollLatencySum;
+#endif
 
     // This state is only used privately by pollOnce and does not require a lock since
     // it runs on a single thread.
@@ -204,6 +258,8 @@
     size_t mResponseIndex;
 
     int pollInner(int timeoutMillis);
+    void awoken();
+    void pushResponse(int events, const Request& request);
 
     static void initTLSKey();
     static void threadDestructor(void *st);
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/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
index d2dd6eb..a5363d6 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -19,16 +19,17 @@
 
 #include <unistd.h>
 #include <fcntl.h>
-#include <sys/epoll.h>
 
 
 namespace android {
 
+#ifdef LOOPER_USES_EPOLL
 // Hint for number of file descriptors to be associated with the epoll instance.
 static const int EPOLL_SIZE_HINT = 8;
 
 // Maximum number of file descriptors for which to retrieve poll events each iteration.
 static const int EPOLL_MAX_EVENTS = 16;
+#endif
 
 static pthread_once_t gTLSOnce = PTHREAD_ONCE_INIT;
 static pthread_key_t gTLSKey = 0;
@@ -36,9 +37,6 @@
 Looper::Looper(bool allowNonCallbacks) :
         mAllowNonCallbacks(allowNonCallbacks),
         mResponseIndex(0) {
-    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
-    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
-
     int wakeFds[2];
     int result = pipe(wakeFds);
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
@@ -54,6 +52,11 @@
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
             errno);
 
+#ifdef LOOPER_USES_EPOLL
+    // Allocate the epoll instance and register the wake pipe.
+    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
+    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
+
     struct epoll_event eventItem;
     memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
     eventItem.events = EPOLLIN;
@@ -61,12 +64,45 @@
     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
             errno);
+#else
+    // Add the wake pipe to the head of the request list with a null callback.
+    struct pollfd requestedFd;
+    requestedFd.fd = mWakeReadPipeFd;
+    requestedFd.events = POLLIN;
+    mRequestedFds.push(requestedFd);
+
+    Request request;
+    request.fd = mWakeReadPipeFd;
+    request.callback = NULL;
+    request.ident = 0;
+    request.data = NULL;
+    mRequests.push(request);
+
+    mPolling = false;
+    mWaiters = 0;
+#endif
+
+#ifdef LOOPER_STATISTICS
+    mPendingWakeTime = -1;
+    mPendingWakeCount = 0;
+    mSampledWakeCycles = 0;
+    mSampledWakeCountSum = 0;
+    mSampledWakeLatencySum = 0;
+
+    mSampledPolls = 0;
+    mSampledZeroPollCount = 0;
+    mSampledZeroPollLatencySum = 0;
+    mSampledTimeoutPollCount = 0;
+    mSampledTimeoutPollLatencySum = 0;
+#endif
 }
 
 Looper::~Looper() {
     close(mWakeReadPipeFd);
     close(mWakeWritePipeFd);
+#ifdef LOOPER_USES_EPOLL
     close(mEpollFd);
+#endif
 }
 
 void Looper::initTLSKey() {
@@ -157,45 +193,61 @@
 #if DEBUG_POLL_AND_WAKE
     LOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
 #endif
+
+    int result = ALOOPER_POLL_WAKE;
+    mResponses.clear();
+    mResponseIndex = 0;
+
+#ifdef LOOPER_STATISTICS
+    nsecs_t pollStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+
+#ifdef LOOPER_USES_EPOLL
     struct epoll_event eventItems[EPOLL_MAX_EVENTS];
     int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+    bool acquiredLock = false;
+#else
+    // Wait for wakeAndLock() waiters to run then set mPolling to true.
+    mLock.lock();
+    while (mWaiters != 0) {
+        mResume.wait(mLock);
+    }
+    mPolling = true;
+    mLock.unlock();
+
+    size_t requestedCount = mRequestedFds.size();
+    int eventCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);
+#endif
+
     if (eventCount < 0) {
         if (errno == EINTR) {
-            return ALOOPER_POLL_WAKE;
+            goto Done;
         }
 
         LOGW("Poll failed with an unexpected error, errno=%d", errno);
-        return ALOOPER_POLL_ERROR;
+        result = ALOOPER_POLL_ERROR;
+        goto Done;
     }
 
     if (eventCount == 0) {
 #if DEBUG_POLL_AND_WAKE
         LOGD("%p ~ pollOnce - timeout", this);
 #endif
-        return ALOOPER_POLL_TIMEOUT;
+        result = ALOOPER_POLL_TIMEOUT;
+        goto Done;
     }
 
-    int result = ALOOPER_POLL_WAKE;
-    mResponses.clear();
-    mResponseIndex = 0;
-
 #if DEBUG_POLL_AND_WAKE
     LOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
 #endif
-    bool acquiredLock = false;
+
+#ifdef LOOPER_USES_EPOLL
     for (int i = 0; i < eventCount; i++) {
         int fd = eventItems[i].data.fd;
         uint32_t epollEvents = eventItems[i].events;
         if (fd == mWakeReadPipeFd) {
             if (epollEvents & EPOLLIN) {
-#if DEBUG_POLL_AND_WAKE
-                LOGD("%p ~ pollOnce - awoken", this);
-#endif
-                char buffer[16];
-                ssize_t nRead;
-                do {
-                    nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
-                } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+                awoken();
             } else {
                 LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
             }
@@ -212,11 +264,7 @@
                 if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
                 if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
                 if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
-
-                Response response;
-                response.events = events;
-                response.request = mRequests.valueAt(requestIndex);
-                mResponses.push(response);
+                pushResponse(events, mRequests.valueAt(requestIndex));
             } else {
                 LOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                         "no longer registered.", epollEvents, fd);
@@ -226,6 +274,66 @@
     if (acquiredLock) {
         mLock.unlock();
     }
+Done: ;
+#else
+    for (size_t i = 0; i < requestedCount; i++) {
+        const struct pollfd& requestedFd = mRequestedFds.itemAt(i);
+
+        short pollEvents = requestedFd.revents;
+        if (pollEvents) {
+            if (requestedFd.fd == mWakeReadPipeFd) {
+                if (pollEvents & POLLIN) {
+                    awoken();
+                } else {
+                    LOGW("Ignoring unexpected poll events 0x%x on wake read pipe.", pollEvents);
+                }
+            } else {
+                int events = 0;
+                if (pollEvents & POLLIN) events |= ALOOPER_EVENT_INPUT;
+                if (pollEvents & POLLOUT) events |= ALOOPER_EVENT_OUTPUT;
+                if (pollEvents & POLLERR) events |= ALOOPER_EVENT_ERROR;
+                if (pollEvents & POLLHUP) events |= ALOOPER_EVENT_HANGUP;
+                if (pollEvents & POLLNVAL) events |= ALOOPER_EVENT_INVALID;
+                pushResponse(events, mRequests.itemAt(i));
+            }
+            if (--eventCount == 0) {
+                break;
+            }
+        }
+    }
+
+Done:
+    // Set mPolling to false and wake up the wakeAndLock() waiters.
+    mLock.lock();
+    mPolling = false;
+    if (mWaiters != 0) {
+        mAwake.broadcast();
+    }
+    mLock.unlock();
+#endif
+
+#ifdef LOOPER_STATISTICS
+    nsecs_t pollEndTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mSampledPolls += 1;
+    if (timeoutMillis == 0) {
+        mSampledZeroPollCount += 1;
+        mSampledZeroPollLatencySum += pollEndTime - pollStartTime;
+    } else if (timeoutMillis > 0 && result == ALOOPER_POLL_TIMEOUT) {
+        mSampledTimeoutPollCount += 1;
+        mSampledTimeoutPollLatencySum += pollEndTime - pollStartTime
+                - milliseconds_to_nanoseconds(timeoutMillis);
+    }
+    if (mSampledPolls == SAMPLED_POLLS_TO_AGGREGATE) {
+        LOGD("%p ~ poll latency statistics: %0.3fms zero timeout, %0.3fms non-zero timeout", this,
+                0.000001f * float(mSampledZeroPollLatencySum) / mSampledZeroPollCount,
+                0.000001f * float(mSampledTimeoutPollLatencySum) / mSampledTimeoutPollCount);
+        mSampledPolls = 0;
+        mSampledZeroPollCount = 0;
+        mSampledZeroPollLatencySum = 0;
+        mSampledTimeoutPollCount = 0;
+        mSampledTimeoutPollLatencySum = 0;
+    }
+#endif
 
     for (size_t i = 0; i < mResponses.size(); i++) {
         const Response& response = mResponses.itemAt(i);
@@ -278,6 +386,13 @@
     LOGD("%p ~ wake", this);
 #endif
 
+#ifdef LOOPER_STATISTICS
+    // FIXME: Possible race with awoken() but this code is for testing only and is rarely enabled.
+    if (mPendingWakeCount++ == 0) {
+        mPendingWakeTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    }
+#endif
+
     ssize_t nWrite;
     do {
         nWrite = write(mWakeWritePipeFd, "W", 1);
@@ -290,23 +405,51 @@
     }
 }
 
+void Looper::awoken() {
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ awoken", this);
+#endif
+
+#ifdef LOOPER_STATISTICS
+    if (mPendingWakeCount == 0) {
+        LOGD("%p ~ awoken: spurious!", this);
+    } else {
+        mSampledWakeCycles += 1;
+        mSampledWakeCountSum += mPendingWakeCount;
+        mSampledWakeLatencySum += systemTime(SYSTEM_TIME_MONOTONIC) - mPendingWakeTime;
+        mPendingWakeCount = 0;
+        mPendingWakeTime = -1;
+        if (mSampledWakeCycles == SAMPLED_WAKE_CYCLES_TO_AGGREGATE) {
+            LOGD("%p ~ wake statistics: %0.3fms wake latency, %0.3f wakes per cycle", this,
+                    0.000001f * float(mSampledWakeLatencySum) / mSampledWakeCycles,
+                    float(mSampledWakeCountSum) / mSampledWakeCycles);
+            mSampledWakeCycles = 0;
+            mSampledWakeCountSum = 0;
+            mSampledWakeLatencySum = 0;
+        }
+    }
+#endif
+
+    char buffer[16];
+    ssize_t nRead;
+    do {
+        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+}
+
+void Looper::pushResponse(int events, const Request& request) {
+    Response response;
+    response.events = events;
+    response.request = request;
+    mResponses.push(response);
+}
+
 int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data) {
 #if DEBUG_CALLBACKS
     LOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
             events, callback, data);
 #endif
 
-    int epollEvents = 0;
-    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
-    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
-    if (events & ALOOPER_EVENT_ERROR) epollEvents |= EPOLLERR;
-    if (events & ALOOPER_EVENT_HANGUP) epollEvents |= EPOLLHUP;
-
-    if (epollEvents == 0) {
-        LOGE("Invalid attempt to set a callback with no selected poll events.");
-        return -1;
-    }
-
     if (! callback) {
         if (! mAllowNonCallbacks) {
             LOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
@@ -319,6 +462,11 @@
         }
     }
 
+#ifdef LOOPER_USES_EPOLL
+    int epollEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
+    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
+
     { // acquire lock
         AutoMutex _l(mLock);
 
@@ -350,6 +498,33 @@
             mRequests.replaceValueAt(requestIndex, request);
         }
     } // release lock
+#else
+    int pollEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) pollEvents |= POLLIN;
+    if (events & ALOOPER_EVENT_OUTPUT) pollEvents |= POLLOUT;
+
+    wakeAndLock(); // acquire lock
+
+    struct pollfd requestedFd;
+    requestedFd.fd = fd;
+    requestedFd.events = pollEvents;
+
+    Request request;
+    request.fd = fd;
+    request.ident = ident;
+    request.callback = callback;
+    request.data = data;
+    ssize_t index = getRequestIndexLocked(fd);
+    if (index < 0) {
+        mRequestedFds.push(requestedFd);
+        mRequests.push(request);
+    } else {
+        mRequestedFds.replaceAt(requestedFd, size_t(index));
+        mRequests.replaceAt(request, size_t(index));
+    }
+
+    mLock.unlock(); // release lock
+#endif
     return 1;
 }
 
@@ -358,6 +533,7 @@
     LOGD("%p ~ removeFd - fd=%d", this, fd);
 #endif
 
+#ifdef LOOPER_USES_EPOLL
     { // acquire lock
         AutoMutex _l(mLock);
         ssize_t requestIndex = mRequests.indexOfKey(fd);
@@ -372,8 +548,49 @@
         }
 
         mRequests.removeItemsAt(requestIndex);
-    } // request lock
+    } // release lock
     return 1;
+#else
+    wakeAndLock(); // acquire lock
+
+    ssize_t index = getRequestIndexLocked(fd);
+    if (index >= 0) {
+        mRequestedFds.removeAt(size_t(index));
+        mRequests.removeAt(size_t(index));
+    }
+
+    mLock.unlock(); // release lock
+    return index >= 0;
+#endif
 }
 
+#ifndef LOOPER_USES_EPOLL
+ssize_t Looper::getRequestIndexLocked(int fd) {
+    size_t requestCount = mRequestedFds.size();
+
+    for (size_t i = 0; i < requestCount; i++) {
+        if (mRequestedFds.itemAt(i).fd == fd) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+void Looper::wakeAndLock() {
+    mLock.lock();
+
+    mWaiters += 1;
+    while (mPolling) {
+        wake();
+        mAwake.wait(mLock);
+    }
+
+    mWaiters -= 1;
+    if (mWaiters == 0) {
+        mResume.signal();
+    }
+}
+#endif
+
 } // namespace android
diff --git a/libs/utils/tests/Looper_test.cpp b/libs/utils/tests/Looper_test.cpp
index afc92f8..cea1313 100644
--- a/libs/utils/tests/Looper_test.cpp
+++ b/libs/utils/tests/Looper_test.cpp
@@ -354,14 +354,6 @@
             << "addFd should return 1 because FD was added";
 }
 
-TEST_F(LooperTest, AddFd_WhenEventsIsZero_ReturnsError) {
-    Pipe pipe;
-    int result = mLooper->addFd(pipe.receiveFd, 0, 0, NULL, NULL);
-
-    EXPECT_EQ(-1, result)
-            << "addFd should return -1 because arguments were invalid";
-}
-
 TEST_F(LooperTest, AddFd_WhenIdentIsNegativeAndCallbackIsNull_ReturnsError) {
     Pipe pipe;
     int result = mLooper->addFd(pipe.receiveFd, -1, ALOOPER_EVENT_INPUT, NULL, NULL);
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 bfc23d4..ff28f3b 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -561,6 +561,39 @@
     postBufferingEvent_l();
 }
 
+void AwesomePlayer::partial_reset_l() {
+    // Only reset the video renderer and shut down the video decoder.
+    // Then instantiate a new video decoder and resume video playback.
+
+    mVideoRenderer.clear();
+
+    if (mLastVideoBuffer) {
+        mLastVideoBuffer->release();
+        mLastVideoBuffer = NULL;
+    }
+
+    if (mVideoBuffer) {
+        mVideoBuffer->release();
+        mVideoBuffer = NULL;
+    }
+
+    {
+        mVideoSource->stop();
+
+        // The following hack is necessary to ensure that the OMX
+        // component is completely released by the time we may try
+        // to instantiate it again.
+        wp<MediaSource> tmp = mVideoSource;
+        mVideoSource.clear();
+        while (tmp.promote() != NULL) {
+            usleep(1000);
+        }
+        IPCThreadState::self()->flushCommands();
+    }
+
+    CHECK_EQ(OK, initVideoDecoder(OMXCodec::kIgnoreCodecSpecificData));
+}
+
 void AwesomePlayer::onStreamDone() {
     // Posted whenever any stream finishes playing.
 
@@ -570,7 +603,21 @@
     }
     mStreamDoneEventPending = false;
 
-    if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
+    if (mStreamDoneStatus == INFO_DISCONTINUITY) {
+        // This special status is returned because an http live stream's
+        // video stream switched to a different bandwidth at this point
+        // and future data may have been encoded using different parameters.
+        // This requires us to shutdown the video decoder and reinstantiate
+        // a fresh one.
+
+        LOGV("INFO_DISCONTINUITY");
+
+        CHECK(mVideoSource != NULL);
+
+        partial_reset_l();
+        postVideoEvent_l();
+        return;
+    } else if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
         LOGV("MEDIA_ERROR %d", mStreamDoneStatus);
 
         notifyListener_l(
@@ -821,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);
     }
@@ -831,12 +876,19 @@
     return OK;
 }
 
+// static
+void AwesomePlayer::OnRTSPSeekDoneWrapper(void *cookie) {
+    static_cast<AwesomePlayer *>(cookie)->onRTSPSeekDone();
+}
+
+void AwesomePlayer::onRTSPSeekDone() {
+    notifyListener_l(MEDIA_SEEK_COMPLETE);
+    mSeekNotificationSent = true;
+}
+
 status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
     if (mRTSPController != NULL) {
-        mRTSPController->seek(timeUs);
-
-        notifyListener_l(MEDIA_SEEK_COMPLETE);
-        mSeekNotificationSent = true;
+        mRTSPController->seekAsync(timeUs, OnRTSPSeekDoneWrapper, this);
         return OK;
     }
 
@@ -939,8 +991,7 @@
     mVideoTrack = source;
 }
 
-status_t AwesomePlayer::initVideoDecoder() {
-    uint32_t flags = 0;
+status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
     mVideoSource = OMXCodec::Create(
             mClient.interface(), mVideoTrack->getFormat(),
             false, // createEncoder
@@ -1571,21 +1622,30 @@
 
     if (mLastVideoBuffer) {
         size_t size = mLastVideoBuffer->range_length();
+
         if (size) {
-            state->mLastVideoFrameSize = size;
-            state->mLastVideoFrame = malloc(size);
-            memcpy(state->mLastVideoFrame,
-                   (const uint8_t *)mLastVideoBuffer->data()
-                        + mLastVideoBuffer->range_offset(),
-                   size);
+            int32_t unreadable;
+            if (!mLastVideoBuffer->meta_data()->findInt32(
+                        kKeyIsUnreadable, &unreadable)
+                    || unreadable == 0) {
+                state->mLastVideoFrameSize = size;
+                state->mLastVideoFrame = malloc(size);
+                memcpy(state->mLastVideoFrame,
+                       (const uint8_t *)mLastVideoBuffer->data()
+                            + mLastVideoBuffer->range_offset(),
+                       size);
 
-            state->mVideoWidth = mVideoWidth;
-            state->mVideoHeight = mVideoHeight;
+                state->mVideoWidth = mVideoWidth;
+                state->mVideoHeight = mVideoHeight;
 
-            sp<MetaData> meta = mVideoSource->getFormat();
-            CHECK(meta->findInt32(kKeyColorFormat, &state->mColorFormat));
-            CHECK(meta->findInt32(kKeyWidth, &state->mDecodedWidth));
-            CHECK(meta->findInt32(kKeyHeight, &state->mDecodedHeight));
+                sp<MetaData> meta = mVideoSource->getFormat();
+                CHECK(meta->findInt32(kKeyColorFormat, &state->mColorFormat));
+                CHECK(meta->findInt32(kKeyWidth, &state->mDecodedWidth));
+                CHECK(meta->findInt32(kKeyHeight, &state->mDecodedHeight));
+            } else {
+                LOGV("Unable to save last video frame, we have no access to "
+                     "the decoded video data.");
+            }
         }
     }
 
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/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 76c8870..4648ad3 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -346,7 +346,8 @@
 }
 
 // static
-uint32_t OMXCodec::getComponentQuirks(const char *componentName) {
+uint32_t OMXCodec::getComponentQuirks(
+        const char *componentName, bool isEncoder) {
     uint32_t quirks = 0;
 
     if (!strcmp(componentName, "OMX.PV.avcdec")) {
@@ -404,6 +405,13 @@
         quirks |= kInputBufferSizesAreBogus;
     }
 
+    if (!strncmp(componentName, "OMX.SEC.", 8) && !isEncoder) {
+        // These output buffers contain no video data, just some
+        // opaque information that allows the overlay to display their
+        // contents.
+        quirks |= kOutputBuffersAreUnreadable;
+    }
+
     return quirks;
 }
 
@@ -490,13 +498,13 @@
             LOGV("Successfully allocated OMX node '%s'", componentName);
 
             sp<OMXCodec> codec = new OMXCodec(
-                    omx, node, getComponentQuirks(componentName),
+                    omx, node, getComponentQuirks(componentName, createEncoder),
                     createEncoder, mime, componentName,
                     source);
 
             observer->setCodec(codec);
 
-            err = codec->configureCodec(meta);
+            err = codec->configureCodec(meta, flags);
 
             if (err == OK) {
                 return codec;
@@ -509,93 +517,95 @@
     return NULL;
 }
 
-status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
-    uint32_t type;
-    const void *data;
-    size_t size;
-    if (meta->findData(kKeyESDS, &type, &data, &size)) {
-        ESDS esds((const char *)data, size);
-        CHECK_EQ(esds.InitCheck(), OK);
+status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) {
+    if (!(flags & kIgnoreCodecSpecificData)) {
+        uint32_t type;
+        const void *data;
+        size_t size;
+        if (meta->findData(kKeyESDS, &type, &data, &size)) {
+            ESDS esds((const char *)data, size);
+            CHECK_EQ(esds.InitCheck(), OK);
 
-        const void *codec_specific_data;
-        size_t codec_specific_data_size;
-        esds.getCodecSpecificInfo(
-                &codec_specific_data, &codec_specific_data_size);
+            const void *codec_specific_data;
+            size_t codec_specific_data_size;
+            esds.getCodecSpecificInfo(
+                    &codec_specific_data, &codec_specific_data_size);
 
-        addCodecSpecificData(
-                codec_specific_data, codec_specific_data_size);
-    } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
-        // Parse the AVCDecoderConfigurationRecord
+            addCodecSpecificData(
+                    codec_specific_data, codec_specific_data_size);
+        } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
+            // Parse the AVCDecoderConfigurationRecord
 
-        const uint8_t *ptr = (const uint8_t *)data;
+            const uint8_t *ptr = (const uint8_t *)data;
 
-        CHECK(size >= 7);
-        CHECK_EQ(ptr[0], 1);  // configurationVersion == 1
-        uint8_t profile = ptr[1];
-        uint8_t level = ptr[3];
+            CHECK(size >= 7);
+            CHECK_EQ(ptr[0], 1);  // configurationVersion == 1
+            uint8_t profile = ptr[1];
+            uint8_t level = ptr[3];
 
-        // There is decodable content out there that fails the following
-        // assertion, let's be lenient for now...
-        // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
+            // There is decodable content out there that fails the following
+            // assertion, let's be lenient for now...
+            // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
 
-        size_t lengthSize = 1 + (ptr[4] & 3);
+            size_t lengthSize = 1 + (ptr[4] & 3);
 
-        // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
-        // violates it...
-        // CHECK((ptr[5] >> 5) == 7);  // reserved
+            // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
+            // violates it...
+            // CHECK((ptr[5] >> 5) == 7);  // reserved
 
-        size_t numSeqParameterSets = ptr[5] & 31;
+            size_t numSeqParameterSets = ptr[5] & 31;
 
-        ptr += 6;
-        size -= 6;
+            ptr += 6;
+            size -= 6;
 
-        for (size_t i = 0; i < numSeqParameterSets; ++i) {
-            CHECK(size >= 2);
-            size_t length = U16_AT(ptr);
+            for (size_t i = 0; i < numSeqParameterSets; ++i) {
+                CHECK(size >= 2);
+                size_t length = U16_AT(ptr);
 
-            ptr += 2;
-            size -= 2;
+                ptr += 2;
+                size -= 2;
 
-            CHECK(size >= length);
+                CHECK(size >= length);
 
-            addCodecSpecificData(ptr, length);
+                addCodecSpecificData(ptr, length);
 
-            ptr += length;
-            size -= length;
-        }
+                ptr += length;
+                size -= length;
+            }
 
-        CHECK(size >= 1);
-        size_t numPictureParameterSets = *ptr;
-        ++ptr;
-        --size;
+            CHECK(size >= 1);
+            size_t numPictureParameterSets = *ptr;
+            ++ptr;
+            --size;
 
-        for (size_t i = 0; i < numPictureParameterSets; ++i) {
-            CHECK(size >= 2);
-            size_t length = U16_AT(ptr);
+            for (size_t i = 0; i < numPictureParameterSets; ++i) {
+                CHECK(size >= 2);
+                size_t length = U16_AT(ptr);
 
-            ptr += 2;
-            size -= 2;
+                ptr += 2;
+                size -= 2;
 
-            CHECK(size >= length);
+                CHECK(size >= length);
 
-            addCodecSpecificData(ptr, length);
+                addCodecSpecificData(ptr, length);
 
-            ptr += length;
-            size -= length;
-        }
+                ptr += length;
+                size -= length;
+            }
 
-        CODEC_LOGV(
-                "AVC profile = %d (%s), level = %d",
-                (int)profile, AVCProfileToString(profile), level);
+            CODEC_LOGV(
+                    "AVC profile = %d (%s), level = %d",
+                    (int)profile, AVCProfileToString(profile), level);
 
-        if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
-            && (profile != kAVCProfileBaseline || level > 30)) {
-            // This stream exceeds the decoder's capabilities. The decoder
-            // does not handle this gracefully and would clobber the heap
-            // and wreak havoc instead...
+            if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
+                && (profile != kAVCProfileBaseline || level > 30)) {
+                // This stream exceeds the decoder's capabilities. The decoder
+                // does not handle this gracefully and would clobber the heap
+                // and wreak havoc instead...
 
-            LOGE("Profile and/or level exceed the decoder's capabilities.");
-            return ERROR_UNSUPPORTED;
+                LOGE("Profile and/or level exceed the decoder's capabilities.");
+                return ERROR_UNSUPPORTED;
+            }
         }
     }
 
@@ -1747,6 +1757,10 @@
                     buffer->meta_data()->setInt32(kKeyIsCodecConfig, true);
                 }
 
+                if (mQuirks & kOutputBuffersAreUnreadable) {
+                    buffer->meta_data()->setInt32(kKeyIsUnreadable, true);
+                }
+
                 buffer->meta_data()->setPointer(
                         kKeyPlatformPrivate,
                         msg.u.extended_buffer_data.platform_private);
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 9d89c20..af9c70c 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -159,6 +159,20 @@
 
     LOGV("successfully decoded video frame.");
 
+    int32_t unreadable;
+    if (buffer->meta_data()->findInt32(kKeyIsUnreadable, &unreadable)
+            && unreadable != 0) {
+        LOGV("video frame is unreadable, decoder does not give us access "
+             "to the video data.");
+
+        buffer->release();
+        buffer = NULL;
+
+        decoder->stop();
+
+        return NULL;
+    }
+
     int64_t timeUs;
     CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
     if (thumbNailTime >= 0) {
diff --git a/media/libstagefright/avc_utils.cpp b/media/libstagefright/avc_utils.cpp
index a8f1104..478e40c 100644
--- a/media/libstagefright/avc_utils.cpp
+++ b/media/libstagefright/avc_utils.cpp
@@ -18,6 +18,9 @@
 
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
 
 namespace android {
 
@@ -41,10 +44,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 +90,212 @@
 
     *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;
+    }
+}
+
+status_t getNextNALUnit(
+        const uint8_t **_data, size_t *_size,
+        const uint8_t **nalStart, size_t *nalSize,
+        bool startCodeFollows) {
+    const uint8_t *data = *_data;
+    size_t size = *_size;
+
+    *nalStart = NULL;
+    *nalSize = 0;
+
+    if (size == 0) {
+        return -EAGAIN;
+    }
+
+    // Skip any number of leading 0x00.
+
+    size_t offset = 0;
+    while (offset < size && data[offset] == 0x00) {
+        ++offset;
+    }
+
+    if (offset == size) {
+        return -EAGAIN;
+    }
+
+    // A valid startcode consists of at least two 0x00 bytes followed by 0x01.
+
+    if (offset < 2 || data[offset] != 0x01) {
+        return ERROR_MALFORMED;
+    }
+
+    ++offset;
+
+    size_t startOffset = offset;
+
+    for (;;) {
+        while (offset < size && data[offset] != 0x01) {
+            ++offset;
+        }
+
+        if (offset == size) {
+            if (startCodeFollows) {
+                offset = size + 2;
+                break;
+            }
+
+            return -EAGAIN;
+        }
+
+        if (data[offset - 1] == 0x00 && data[offset - 2] == 0x00) {
+            break;
+        }
+
+        ++offset;
+    }
+
+    size_t endOffset = offset - 2;
+    while (data[endOffset - 1] == 0x00) {
+        --endOffset;
+    }
+
+    *nalStart = &data[startOffset];
+    *nalSize = endOffset - startOffset;
+
+    if (offset + 2 < size) {
+        *_data = &data[offset - 2];
+        *_size = size - offset + 2;
+    } else {
+        *_data = NULL;
+        *_size = 0;
+    }
+
+    return OK;
+}
+
+static sp<ABuffer> FindNAL(
+        const uint8_t *data, size_t size, unsigned nalType,
+        size_t *stopOffset) {
+    const uint8_t *nalStart;
+    size_t nalSize;
+    while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
+        if ((nalStart[0] & 0x1f) == nalType) {
+            sp<ABuffer> buffer = new ABuffer(nalSize);
+            memcpy(buffer->data(), nalStart, nalSize);
+            return buffer;
+        }
+    }
+
+    return NULL;
+}
+
+sp<MetaData> MakeAVCCodecSpecificData(const sp<ABuffer> &accessUnit) {
+    const uint8_t *data = accessUnit->data();
+    size_t size = accessUnit->size();
+
+    sp<ABuffer> seqParamSet = FindNAL(data, size, 7, NULL);
+    if (seqParamSet == NULL) {
+        return NULL;
+    }
+
+    int32_t width, height;
+    FindAVCDimensions(seqParamSet, &width, &height);
+
+    size_t stopOffset;
+    sp<ABuffer> picParamSet = FindNAL(data, size, 8, &stopOffset);
+    CHECK(picParamSet != NULL);
+
+    size_t csdSize =
+        1 + 3 + 1 + 1
+        + 2 * 1 + seqParamSet->size()
+        + 1 + 2 * 1 + picParamSet->size();
+
+    sp<ABuffer> csd = new ABuffer(csdSize);
+    uint8_t *out = csd->data();
+
+    *out++ = 0x01;  // configurationVersion
+    memcpy(out, seqParamSet->data() + 1, 3);  // profile/level...
+    out += 3;
+    *out++ = (0x3f << 2) | 1;  // lengthSize == 2 bytes
+    *out++ = 0xe0 | 1;
+
+    *out++ = seqParamSet->size() >> 8;
+    *out++ = seqParamSet->size() & 0xff;
+    memcpy(out, seqParamSet->data(), seqParamSet->size());
+    out += seqParamSet->size();
+
+    *out++ = 1;
+
+    *out++ = picParamSet->size() >> 8;
+    *out++ = picParamSet->size() & 0xff;
+    memcpy(out, picParamSet->data(), picParamSet->size());
+
+#if 0
+    LOGI("AVC seq param set");
+    hexdump(seqParamSet->data(), seqParamSet->size());
+#endif
+
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+
+    meta->setData(kKeyAVCC, 0, csd->data(), csd->size());
+    meta->setInt32(kKeyWidth, width);
+    meta->setInt32(kKeyHeight, height);
+
+    LOGI("found AVC codec config (%d x %d)", width, height);
+
+    return meta;
+}
+
+bool IsIDR(const sp<ABuffer> &buffer) {
+    const uint8_t *data = buffer->data();
+    size_t size = buffer->size();
+
+    bool foundIDR = false;
+
+    const uint8_t *nalStart;
+    size_t nalSize;
+    while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
+        CHECK_GT(nalSize, 0u);
+
+        unsigned nalType = nalStart[0] & 0x1f;
+
+        if (nalType == 5) {
+            foundIDR = true;
+            break;
+        }
+    }
+
+    return foundIDR;
 }
 
 }  // 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/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
index 9103927..943a0fc 100644
--- a/media/libstagefright/httplive/LiveSource.cpp
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
 #define LOG_TAG "LiveSource"
 #include <utils/Log.h>
 
@@ -22,18 +23,21 @@
 #include "include/NuHTTPDataSource.h"
 
 #include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/FileSource.h>
 #include <media/stagefright/MediaDebug.h>
 
 namespace android {
 
 LiveSource::LiveSource(const char *url)
-    : mURL(url),
+    : mMasterURL(url),
       mInitCheck(NO_INIT),
       mPlaylistIndex(0),
       mLastFetchTimeUs(-1),
       mSource(new NuHTTPDataSource),
       mSourceSize(0),
-      mOffsetBias(0) {
+      mOffsetBias(0),
+      mSignalDiscontinuity(false),
+      mPrevBandwidthIndex(-1) {
     if (switchToNext()) {
         mInitCheck = OK;
     }
@@ -46,21 +50,129 @@
     return mInitCheck;
 }
 
-bool LiveSource::loadPlaylist() {
+// static
+int LiveSource::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b) {
+    if (a->mBandwidth < b->mBandwidth) {
+        return -1;
+    } else if (a->mBandwidth == b->mBandwidth) {
+        return 0;
+    }
+
+    return 1;
+}
+
+static double uniformRand() {
+    return (double)rand() / RAND_MAX;
+}
+
+bool LiveSource::loadPlaylist(bool fetchMaster) {
+    mSignalDiscontinuity = false;
+
     mPlaylist.clear();
     mPlaylistIndex = 0;
 
-    sp<ABuffer> buffer;
-    status_t err = fetchM3U(mURL.c_str(), &buffer);
+    if (fetchMaster) {
+        mPrevBandwidthIndex = -1;
 
-    if (err != OK) {
-        return false;
+        sp<ABuffer> buffer;
+        status_t err = fetchM3U(mMasterURL.c_str(), &buffer);
+
+        if (err != OK) {
+            return false;
+        }
+
+        mPlaylist = new M3UParser(
+                mMasterURL.c_str(), buffer->data(), buffer->size());
+
+        if (mPlaylist->initCheck() != OK) {
+            return false;
+        }
+
+        if (mPlaylist->isVariantPlaylist()) {
+            for (size_t i = 0; i < mPlaylist->size(); ++i) {
+                BandwidthItem item;
+
+                sp<AMessage> meta;
+                mPlaylist->itemAt(i, &item.mURI, &meta);
+
+                unsigned long bandwidth;
+                CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
+
+                mBandwidthItems.push(item);
+            }
+            mPlaylist.clear();
+
+            // fall through
+            if (mBandwidthItems.size() == 0) {
+                return false;
+            }
+
+            mBandwidthItems.sort(SortByBandwidth);
+
+            for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
+                const BandwidthItem &item = mBandwidthItems.itemAt(i);
+                LOGV("item #%d: %s", i, item.mURI.c_str());
+            }
+        }
     }
 
-    mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
+    if (mBandwidthItems.size() > 0) {
+#if 0
+        // Change bandwidth at random()
+        size_t index = uniformRand() * mBandwidthItems.size();
+#elif 0
+        // There's a 50% chance to stay on the current bandwidth and
+        // a 50% chance to switch to the next higher bandwidth (wrapping around
+        // to lowest)
+        size_t index;
+        if (uniformRand() < 0.5) {
+            index = mPrevBandwidthIndex < 0 ? 0 : (size_t)mPrevBandwidthIndex;
+        } else {
+            if (mPrevBandwidthIndex < 0) {
+                index = 0;
+            } else {
+                index = mPrevBandwidthIndex + 1;
+                if (index == mBandwidthItems.size()) {
+                    index = 0;
+                }
+            }
+        }
+#else
+        // Stay on the lowest bandwidth available.
+        size_t index = 0;  // Lowest bandwidth stream
+#endif
 
-    if (mPlaylist->initCheck() != OK) {
-        return false;
+        mURL = mBandwidthItems.editItemAt(index).mURI;
+
+        if (mPrevBandwidthIndex >= 0 && (size_t)mPrevBandwidthIndex != index) {
+            // If we switched streams because of bandwidth changes,
+            // we'll signal this discontinuity by inserting a
+            // special transport stream packet into the stream.
+            mSignalDiscontinuity = true;
+        }
+
+        mPrevBandwidthIndex = index;
+    } else {
+        mURL = mMasterURL;
+    }
+
+    if (mPlaylist == NULL) {
+        sp<ABuffer> buffer;
+        status_t err = fetchM3U(mURL.c_str(), &buffer);
+
+        if (err != OK) {
+            return false;
+        }
+
+        mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
+
+        if (mPlaylist->initCheck() != OK) {
+            return false;
+        }
+
+        if (mPlaylist->isVariantPlaylist()) {
+            return false;
+        }
     }
 
     if (!mPlaylist->meta()->findInt32(
@@ -79,6 +191,8 @@
 }
 
 bool LiveSource::switchToNext() {
+    mSignalDiscontinuity = false;
+
     mOffsetBias += mSourceSize;
     mSourceSize = 0;
 
@@ -87,7 +201,7 @@
         int32_t nextSequenceNumber =
             mPlaylistIndex + mFirstItemSequenceNumber;
 
-        if (!loadPlaylist()) {
+        if (!loadPlaylist(mLastFetchTimeUs < 0)) {
             LOGE("failed to reload playlist");
             return false;
         }
@@ -111,35 +225,62 @@
     }
 
     AString uri;
-    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri));
-    LOGI("switching to %s", uri.c_str());
+    sp<AMessage> itemMeta;
+    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
+    LOGV("switching to %s", uri.c_str());
 
     if (mSource->connect(uri.c_str()) != OK
             || mSource->getSize(&mSourceSize) != OK) {
         return false;
     }
 
+    int32_t val;
+    if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
+        mSignalDiscontinuity = true;
+    }
+
     mPlaylistIndex++;
     return true;
 }
 
+static const ssize_t kHeaderSize = 188;
+
 ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
     CHECK(offset >= mOffsetBias);
     offset -= mOffsetBias;
 
-    if (offset >= mSourceSize) {
-        CHECK_EQ(offset, mSourceSize);
+    off_t delta = mSignalDiscontinuity ? kHeaderSize : 0;
 
-        offset -= mSourceSize;
+    if (offset >= mSourceSize + delta) {
+        CHECK_EQ(offset, mSourceSize + delta);
+
+        offset -= mSourceSize + delta;
         if (!switchToNext()) {
             return ERROR_END_OF_STREAM;
         }
+
+        if (mSignalDiscontinuity) {
+            LOGV("switchToNext changed streams");
+        } else {
+            LOGV("switchToNext stayed within the same stream");
+        }
+
+        mOffsetBias += delta;
+
+        delta = mSignalDiscontinuity ? kHeaderSize : 0;
+    }
+
+    if (offset < delta) {
+        size_t avail = delta - offset;
+        memset(data, 0, avail);
+        return avail;
     }
 
     size_t numRead = 0;
     while (numRead < size) {
         ssize_t n = mSource->readAt(
-                offset + numRead, (uint8_t *)data + numRead, size - numRead);
+                offset + numRead - delta,
+                (uint8_t *)data + numRead, size - numRead);
 
         if (n <= 0) {
             break;
@@ -154,14 +295,24 @@
 status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
     *out = NULL;
 
-    status_t err = mSource->connect(url);
+    sp<DataSource> source;
 
-    if (err != OK) {
-        return err;
+    if (!strncasecmp(url, "file://", 7)) {
+        source = new FileSource(url + 7);
+    } else {
+        CHECK(!strncasecmp(url, "http://", 7));
+
+        status_t err = mSource->connect(url);
+
+        if (err != OK) {
+            return err;
+        }
+
+        source = mSource;
     }
 
     off_t size;
-    err = mSource->getSize(&size);
+    status_t err = source->getSize(&size);
 
     if (err != OK) {
         return err;
@@ -170,7 +321,7 @@
     sp<ABuffer> buffer = new ABuffer(size);
     size_t offset = 0;
     while (offset < (size_t)size) {
-        ssize_t n = mSource->readAt(
+        ssize_t n = source->readAt(
                 offset, buffer->data() + offset, size - offset);
 
         if (n <= 0) {
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index edd8648..0d7daa9 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -74,7 +74,8 @@
 static bool MakeURL(const char *baseURL, const char *url, AString *out) {
     out->clear();
 
-    if (strncasecmp("http://", baseURL, 7)) {
+    if (strncasecmp("http://", baseURL, 7)
+            && strncasecmp("file://", baseURL, 7)) {
         // Base URL must be absolute
         return false;
     }
@@ -128,7 +129,12 @@
             line.setTo(&data[offset], offsetLF - offset);
         }
 
-        LOGI("#%s#", line.c_str());
+        // LOGI("#%s#", line.c_str());
+
+        if (line.empty()) {
+            offset = offsetLF + 1;
+            continue;
+        }
 
         if (lineNo == 0 && line == "#EXTM3U") {
             mIsExtM3U = true;
@@ -152,11 +158,20 @@
                     return ERROR_MALFORMED;
                 }
                 err = parseMetaData(line, &itemMeta, "duration");
+            } else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
+                if (mIsVariantPlaylist) {
+                    return ERROR_MALFORMED;
+                }
+                if (itemMeta == NULL) {
+                    itemMeta = new AMessage;
+                }
+                itemMeta->setInt32("discontinuity", true);
             } else if (line.startsWith("#EXT-X-STREAM-INF")) {
                 if (mMeta != NULL) {
                     return ERROR_MALFORMED;
                 }
                 mIsVariantPlaylist = true;
+                err = parseStreamInf(line, &itemMeta);
             }
 
             if (err != OK) {
@@ -215,6 +230,61 @@
 }
 
 // static
+status_t M3UParser::parseStreamInf(
+        const AString &line, sp<AMessage> *meta) {
+    ssize_t colonPos = line.find(":");
+
+    if (colonPos < 0) {
+        return ERROR_MALFORMED;
+    }
+
+    size_t offset = colonPos + 1;
+
+    while (offset < line.size()) {
+        ssize_t end = line.find(",", offset);
+        if (end < 0) {
+            end = line.size();
+        }
+
+        AString attr(line, offset, end - offset);
+        attr.trim();
+
+        offset = end + 1;
+
+        ssize_t equalPos = attr.find("=");
+        if (equalPos < 0) {
+            continue;
+        }
+
+        AString key(attr, 0, equalPos);
+        key.trim();
+
+        AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
+        val.trim();
+
+        LOGV("key=%s value=%s", key.c_str(), val.c_str());
+
+        if (!strcasecmp("bandwidth", key.c_str())) {
+            const char *s = val.c_str();
+            char *end;
+            unsigned long x = strtoul(s, &end, 10);
+
+            if (end == s || *end != '\0') {
+                // malformed
+                continue;
+            }
+
+            if (meta->get() == NULL) {
+                *meta = new AMessage;
+            }
+            (*meta)->setInt32("bandwidth", x);
+        }
+    }
+
+    return OK;
+}
+
+// static
 status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
     char *end;
     long lval = strtol(s, &end, 10);
diff --git a/media/libstagefright/include/ARTSPController.h b/media/libstagefright/include/ARTSPController.h
index 300d8f7..ce7ffe5 100644
--- a/media/libstagefright/include/ARTSPController.h
+++ b/media/libstagefright/include/ARTSPController.h
@@ -33,7 +33,7 @@
     status_t connect(const char *url);
     void disconnect();
 
-    void seek(int64_t timeUs);
+    void seekAsync(int64_t timeUs, void (*seekDoneCb)(void *), void *cookie);
 
     virtual size_t countTracks();
     virtual sp<MediaSource> getTrack(size_t index);
@@ -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();
 
@@ -53,6 +61,7 @@
     enum {
         kWhatConnectDone    = 'cdon',
         kWhatDisconnectDone = 'ddon',
+        kWhatSeekDone       = 'sdon',
     };
 
     enum State {
@@ -71,6 +80,10 @@
     sp<MyHandler> mHandler;
     sp<AHandlerReflector<ARTSPController> > mReflector;
 
+    void (*mSeekDoneCb)(void *);
+    void *mSeekDoneCookie;
+    int64_t mLastSeekCompletedTimeUs;
+
     DISALLOW_EVIL_CONSTRUCTORS(ARTSPController);
 };
 
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 40ea37d..079adca 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -220,6 +220,7 @@
     status_t setDataSource_l(const sp<DataSource> &dataSource);
     status_t setDataSource_l(const sp<MediaExtractor> &extractor);
     void reset_l();
+    void partial_reset_l();
     status_t seekTo_l(int64_t timeUs);
     status_t pause_l(bool at_eos = false);
     void initRenderer_l();
@@ -231,7 +232,7 @@
     status_t initAudioDecoder();
 
     void setVideoSource(sp<MediaSource> source);
-    status_t initVideoDecoder();
+    status_t initVideoDecoder(uint32_t flags = 0);
 
     void onStreamDone();
 
@@ -250,6 +251,9 @@
 
     static bool ContinuePreparation(void *cookie);
 
+    static void OnRTSPSeekDoneWrapper(void *cookie);
+    void onRTSPSeekDone();
+
     AwesomePlayer(const AwesomePlayer &);
     AwesomePlayer &operator=(const AwesomePlayer &);
 };
diff --git a/media/libstagefright/include/LiveSource.h b/media/libstagefright/include/LiveSource.h
index c55508c..5e89581 100644
--- a/media/libstagefright/include/LiveSource.h
+++ b/media/libstagefright/include/LiveSource.h
@@ -44,6 +44,13 @@
     virtual ~LiveSource();
 
 private:
+    struct BandwidthItem {
+        AString mURI;
+        unsigned long mBandwidth;
+    };
+    Vector<BandwidthItem> mBandwidthItems;
+
+    AString mMasterURL;
     AString mURL;
     status_t mInitCheck;
 
@@ -56,10 +63,15 @@
     off_t mSourceSize;
     off_t mOffsetBias;
 
+    bool mSignalDiscontinuity;
+    ssize_t mPrevBandwidthIndex;
+
     status_t fetchM3U(const char *url, sp<ABuffer> *buffer);
 
+    static int SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b);
+
     bool switchToNext();
-    bool loadPlaylist();
+    bool loadPlaylist(bool fetchMaster);
 
     DISALLOW_EVIL_CONSTRUCTORS(LiveSource);
 };
diff --git a/media/libstagefright/include/M3UParser.h b/media/libstagefright/include/M3UParser.h
index 36553de..69199ab 100644
--- a/media/libstagefright/include/M3UParser.h
+++ b/media/libstagefright/include/M3UParser.h
@@ -61,6 +61,9 @@
     static status_t parseMetaData(
             const AString &line, sp<AMessage> *meta, const char *key);
 
+    static status_t parseStreamInf(
+            const AString &line, sp<AMessage> *meta);
+
     static status_t ParseInt32(const char *s, int32_t *x);
 
     DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
diff --git a/media/libstagefright/include/avc_utils.h b/media/libstagefright/include/avc_utils.h
index 6602852..62cfc36 100644
--- a/media/libstagefright/include/avc_utils.h
+++ b/media/libstagefright/include/avc_utils.h
@@ -29,6 +29,16 @@
 
 unsigned parseUE(ABitReader *br);
 
+status_t getNextNALUnit(
+        const uint8_t **_data, size_t *_size,
+        const uint8_t **nalStart, size_t *nalSize,
+        bool startCodeFollows = false);
+
+struct MetaData;
+sp<MetaData> MakeAVCCodecSpecificData(const sp<ABuffer> &accessUnit);
+
+bool IsIDR(const sp<ABuffer> &accessUnit);
+
 }  // namespace android
 
 #endif  // AVC_UTILS_H_
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index bcaab9f..7c9b83a 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -49,6 +49,8 @@
             unsigned pid, unsigned payload_unit_start_indicator,
             ABitReader *br);
 
+    void signalDiscontinuity();
+
     sp<MediaSource> getSource(SourceType type);
 
 private:
@@ -67,6 +69,8 @@
             unsigned payload_unit_start_indicator,
             ABitReader *br);
 
+    void signalDiscontinuity();
+
     sp<MediaSource> getSource(SourceType type);
 
 protected:
@@ -124,6 +128,12 @@
     return true;
 }
 
+void ATSParser::Program::signalDiscontinuity() {
+    for (size_t i = 0; i < mStreams.size(); ++i) {
+        mStreams.editValueAt(i)->signalDiscontinuity();
+    }
+}
+
 void ATSParser::Program::parseProgramMap(ABitReader *br) {
     unsigned table_id = br->getBits(8);
     LOGV("  table_id = %u", table_id);
@@ -271,6 +281,19 @@
     mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);
 }
 
+void ATSParser::Stream::signalDiscontinuity() {
+    LOGV("Stream discontinuity");
+    mPayloadStarted = false;
+    mBuffer->setRange(0, 0);
+
+    mQueue.clear();
+
+    if (mStreamType == 0x1b && mSource != NULL) {
+        // Don't signal discontinuities on audio streams.
+        mSource->queueDiscontinuity();
+    }
+}
+
 void ATSParser::Stream::parsePES(ABitReader *br) {
     unsigned packet_startcode_prefix = br->getBits(24);
 
@@ -459,7 +482,10 @@
                 mSource = new AnotherPacketSource(meta);
                 mSource->queueAccessUnit(accessUnit);
             }
-        } else {
+        } else if (mQueue.getFormat() != NULL) {
+            // After a discontinuity we invalidate the queue's format
+            // and won't enqueue any access units to the source until
+            // the queue has reestablished the new format.
             mSource->queueAccessUnit(accessUnit);
         }
     }
@@ -489,6 +515,12 @@
     parseTS(&br);
 }
 
+void ATSParser::signalDiscontinuity() {
+    for (size_t i = 0; i < mPrograms.size(); ++i) {
+        mPrograms.editItemAt(i)->signalDiscontinuity();
+    }
+}
+
 void ATSParser::parseProgramAssociationTable(ABitReader *br) {
     unsigned table_id = br->getBits(8);
     LOGV("  table_id = %u", table_id);
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 1e22e7b..9ec6d7b 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -33,6 +33,7 @@
     ATSParser();
 
     void feedTSPacket(const void *data, size_t size);
+    void signalDiscontinuity();
 
     enum SourceType {
         AVC_VIDEO,
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 3d51177..3f76820 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -59,21 +59,26 @@
 
     if (!mBuffers.empty()) {
         const sp<ABuffer> buffer = *mBuffers.begin();
-
-        uint64_t timeUs;
-        CHECK(buffer->meta()->findInt64(
-                    "time", (int64_t *)&timeUs));
-
-        MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
-        mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
-
-        // hexdump(buffer->data(), buffer->size());
-
-        memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
-        *out = mediaBuffer;
-
         mBuffers.erase(mBuffers.begin());
-        return OK;
+
+        int32_t discontinuity;
+        if (buffer->meta()->findInt32("discontinuity", &discontinuity)
+                && discontinuity) {
+            return INFO_DISCONTINUITY;
+        } else {
+            uint64_t timeUs;
+            CHECK(buffer->meta()->findInt64(
+                        "time", (int64_t *)&timeUs));
+
+            MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
+            mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
+
+            // hexdump(buffer->data(), buffer->size());
+
+            memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
+            *out = mediaBuffer;
+            return OK;
+        }
     }
 
     return mEOSResult;
@@ -91,6 +96,15 @@
     mCondition.signal();
 }
 
+void AnotherPacketSource::queueDiscontinuity() {
+    sp<ABuffer> buffer = new ABuffer(0);
+    buffer->meta()->setInt32("discontinuity", true);
+
+    Mutex::Autolock autoLock(mLock);
+    mBuffers.push_back(buffer);
+    mCondition.signal();
+}
+
 void AnotherPacketSource::signalEOS(status_t result) {
     CHECK(result != OK);
 
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index ce83d21..6b43c4e 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -40,6 +40,7 @@
     bool hasBufferAvailable(status_t *finalResult);
 
     void queueAccessUnit(const sp<ABuffer> &buffer);
+    void queueDiscontinuity();
     void signalEOS(status_t result);
 
 protected:
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index d87040b..a13287e 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -40,79 +40,9 @@
     return mFormat;
 }
 
-static status_t getNextNALUnit(
-        const uint8_t **_data, size_t *_size,
-        const uint8_t **nalStart, size_t *nalSize,
-        bool startCodeFollows = false) {
-    const uint8_t *data = *_data;
-    size_t size = *_size;
-
-    *nalStart = NULL;
-    *nalSize = 0;
-
-    if (size == 0) {
-        return -EAGAIN;
-    }
-
-    // Skip any number of leading 0x00.
-
-    size_t offset = 0;
-    while (offset < size && data[offset] == 0x00) {
-        ++offset;
-    }
-
-    if (offset == size) {
-        return -EAGAIN;
-    }
-
-    // A valid startcode consists of at least two 0x00 bytes followed by 0x01.
-
-    if (offset < 2 || data[offset] != 0x01) {
-        return ERROR_MALFORMED;
-    }
-
-    ++offset;
-
-    size_t startOffset = offset;
-
-    for (;;) {
-        while (offset < size && data[offset] != 0x01) {
-            ++offset;
-        }
-
-        if (offset == size) {
-            if (startCodeFollows) {
-                offset = size + 2;
-                break;
-            }
-
-            return -EAGAIN;
-        }
-
-        if (data[offset - 1] == 0x00 && data[offset - 2] == 0x00) {
-            break;
-        }
-
-        ++offset;
-    }
-
-    size_t endOffset = offset - 2;
-    while (data[endOffset - 1] == 0x00) {
-        --endOffset;
-    }
-
-    *nalStart = &data[startOffset];
-    *nalSize = endOffset - startOffset;
-
-    if (offset + 2 < size) {
-        *_data = &data[offset - 2];
-        *_size = size - offset + 2;
-    } else {
-        *_data = NULL;
-        *_size = 0;
-    }
-
-    return OK;
+void ElementaryStreamQueue::clear() {
+    mBuffer->setRange(0, 0);
+    mFormat.clear();
 }
 
 status_t ElementaryStreamQueue::appendData(
@@ -147,7 +77,7 @@
     if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
         neededSize = (neededSize + 65535) & ~65535;
 
-        LOGI("resizing buffer to size %d", neededSize);
+        LOGV("resizing buffer to size %d", neededSize);
 
         sp<ABuffer> buffer = new ABuffer(neededSize);
         if (mBuffer != NULL) {
@@ -428,77 +358,4 @@
     return NULL;
 }
 
-static sp<ABuffer> FindNAL(
-        const uint8_t *data, size_t size, unsigned nalType,
-        size_t *stopOffset) {
-    const uint8_t *nalStart;
-    size_t nalSize;
-    while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
-        if ((nalStart[0] & 0x1f) == nalType) {
-            sp<ABuffer> buffer = new ABuffer(nalSize);
-            memcpy(buffer->data(), nalStart, nalSize);
-            return buffer;
-        }
-    }
-
-    return NULL;
-}
-
-sp<MetaData> ElementaryStreamQueue::MakeAVCCodecSpecificData(
-        const sp<ABuffer> &accessUnit) {
-    const uint8_t *data = accessUnit->data();
-    size_t size = accessUnit->size();
-
-    sp<ABuffer> seqParamSet = FindNAL(data, size, 7, NULL);
-    if (seqParamSet == NULL) {
-        return NULL;
-    }
-
-    int32_t width, height;
-    FindAVCDimensions(seqParamSet, &width, &height);
-
-    size_t stopOffset;
-    sp<ABuffer> picParamSet = FindNAL(data, size, 8, &stopOffset);
-    CHECK(picParamSet != NULL);
-
-    size_t csdSize =
-        1 + 3 + 1 + 1
-        + 2 * 1 + seqParamSet->size()
-        + 1 + 2 * 1 + picParamSet->size();
-
-    sp<ABuffer> csd = new ABuffer(csdSize);
-    uint8_t *out = csd->data();
-
-    *out++ = 0x01;  // configurationVersion
-    memcpy(out, seqParamSet->data() + 1, 3);  // profile/level...
-    out += 3;
-    *out++ = (0x3f << 2) | 1;  // lengthSize == 2 bytes
-    *out++ = 0xe0 | 1;
-
-    *out++ = seqParamSet->size() >> 8;
-    *out++ = seqParamSet->size() & 0xff;
-    memcpy(out, seqParamSet->data(), seqParamSet->size());
-    out += seqParamSet->size();
-
-    *out++ = 1;
-
-    *out++ = picParamSet->size() >> 8;
-    *out++ = picParamSet->size() & 0xff;
-    memcpy(out, picParamSet->data(), picParamSet->size());
-
-#if 0
-    LOGI("AVC seq param set");
-    hexdump(seqParamSet->data(), seqParamSet->size());
-#endif
-
-    sp<MetaData> meta = new MetaData;
-    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
-
-    meta->setData(kKeyAVCC, 0, csd->data(), csd->size());
-    meta->setInt32(kKeyWidth, width);
-    meta->setInt32(kKeyHeight, height);
-
-    return meta;
-}
-
 }  // namespace android
diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h
index d2e87f2..9eaf834 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.h
+++ b/media/libstagefright/mpeg2ts/ESQueue.h
@@ -35,6 +35,7 @@
     ElementaryStreamQueue(Mode mode);
 
     status_t appendData(const void *data, size_t size, int64_t timeUs);
+    void clear();
 
     sp<ABuffer> dequeueAccessUnit();
 
@@ -55,9 +56,6 @@
             unsigned profile, unsigned sampling_freq_index,
             unsigned channel_configuration);
 
-    static sp<MetaData> MakeAVCCodecSpecificData(
-            const sp<ABuffer> &accessUnit);
-
     DISALLOW_EVIL_CONSTRUCTORS(ElementaryStreamQueue);
 };
 
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index c5257bb..0d96bd1 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -165,18 +165,26 @@
     LOGI("haveAudio=%d, haveVideo=%d", haveAudio, haveVideo);
 }
 
+static bool isDiscontinuity(const uint8_t *data, ssize_t size) {
+    return size == 188 && data[0] == 0x00;
+}
+
 status_t MPEG2TSExtractor::feedMore() {
     Mutex::Autolock autoLock(mLock);
 
     uint8_t packet[kTSPacketSize];
     ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);
 
-    if (n < (ssize_t)kTSPacketSize) {
+    if (isDiscontinuity(packet, n)) {
+        LOGI("XXX discontinuity detected");
+        mParser->signalDiscontinuity();
+    } else if (n < (ssize_t)kTSPacketSize) {
         return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;
+    } else {
+        mParser->feedTSPacket(packet, kTSPacketSize);
     }
 
-    mOffset += kTSPacketSize;
-    mParser->feedTSPacket(packet, kTSPacketSize);
+    mOffset += n;
 
     return OK;
 }
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index 78754e6..10cc88b 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -490,6 +490,8 @@
     : mInitCheck(NO_INIT),
       mFormat(new MetaData),
       mEOSResult(OK),
+      mIsAVC(false),
+      mScanForIDR(true),
       mRTPTimeBase(0),
       mNormalPlayTimeBaseUs(0),
       mLastNormalPlayTimeUs(0) {
@@ -509,6 +511,8 @@
 
     mInitCheck = OK;
     if (!strncmp(desc.c_str(), "H264/", 5)) {
+        mIsAVC = true;
+
         mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
 
         int32_t width, height;
@@ -719,6 +723,20 @@
         return;
     }
 
+    if (mScanForIDR && mIsAVC) {
+        // This pretty piece of code ensures that the first access unit
+        // fed to the decoder after stream-start or seek is guaranteed to
+        // be an IDR frame. This is to workaround limitations of a certain
+        // hardware h.264 decoder that requires this to be the case.
+
+        if (!IsIDR(buffer)) {
+            LOGV("skipping AU while scanning for next IDR frame.");
+            return;
+        }
+
+        mScanForIDR = false;
+    }
+
     Mutex::Autolock autoLock(mLock);
     mBuffers.push_back(buffer);
     mCondition.signal();
@@ -735,6 +753,8 @@
 void APacketSource::flushQueue() {
     Mutex::Autolock autoLock(mLock);
     mBuffers.clear();
+
+    mScanForIDR = true;
 }
 
 int64_t APacketSource::getNormalPlayTimeUs() {
diff --git a/media/libstagefright/rtsp/APacketSource.h b/media/libstagefright/rtsp/APacketSource.h
index 076ddc47..7a77fc6 100644
--- a/media/libstagefright/rtsp/APacketSource.h
+++ b/media/libstagefright/rtsp/APacketSource.h
@@ -65,6 +65,9 @@
     List<sp<ABuffer> > mBuffers;
     status_t mEOSResult;
 
+    bool mIsAVC;
+    bool mScanForIDR;
+
     uint32_t mClockRate;
 
     uint32_t mRTPTimeBase;
diff --git a/media/libstagefright/rtsp/ARTSPController.cpp b/media/libstagefright/rtsp/ARTSPController.cpp
index 4c53639..a7563ff 100644
--- a/media/libstagefright/rtsp/ARTSPController.cpp
+++ b/media/libstagefright/rtsp/ARTSPController.cpp
@@ -27,7 +27,10 @@
 
 ARTSPController::ARTSPController(const sp<ALooper> &looper)
     : mState(DISCONNECTED),
-      mLooper(looper) {
+      mLooper(looper),
+      mSeekDoneCb(NULL),
+      mSeekDoneCookie(NULL),
+      mLastSeekCompletedTimeUs(-1) {
     mReflector = new AHandlerReflector<ARTSPController>(this);
     looper->registerHandler(mReflector);
 }
@@ -80,14 +83,31 @@
     mHandler.clear();
 }
 
-void ARTSPController::seek(int64_t timeUs) {
+void ARTSPController::seekAsync(
+        int64_t timeUs,
+        void (*seekDoneCb)(void *), void *cookie) {
     Mutex::Autolock autoLock(mLock);
 
-    if (mState != CONNECTED) {
+    CHECK(seekDoneCb != NULL);
+    CHECK(mSeekDoneCb == NULL);
+
+    // Ignore seek requests that are too soon after the previous one has
+    // completed, we don't want to swamp the server.
+
+    bool tooEarly =
+        mLastSeekCompletedTimeUs >= 0
+            && ALooper::GetNowUs() < mLastSeekCompletedTimeUs + 500000ll;
+
+    if (mState != CONNECTED || tooEarly) {
+        (*seekDoneCb)(cookie);
         return;
     }
 
-    mHandler->seek(timeUs);
+    mSeekDoneCb = seekDoneCb;
+    mSeekDoneCookie = cookie;
+
+    sp<AMessage> msg = new AMessage(kWhatSeekDone, mReflector->id());
+    mHandler->seek(timeUs, msg);
 }
 
 size_t ARTSPController::countTracks() {
@@ -132,6 +152,19 @@
             break;
         }
 
+        case kWhatSeekDone:
+        {
+            LOGI("seek done");
+
+            mLastSeekCompletedTimeUs = ALooper::GetNowUs();
+
+            void (*seekDoneCb)(void *) = mSeekDoneCb;
+            mSeekDoneCb = NULL;
+
+            (*seekDoneCb)(mSeekDoneCookie);
+            break;
+        }
+
         default:
             TRESPASS();
             break;
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index a31b2b2..05dd61b 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -86,8 +86,10 @@
           mFirstAccessUnitNTP(0),
           mNumAccessUnitsReceived(0),
           mCheckPending(false),
+          mCheckGeneration(0),
           mTryTCPInterleaving(false),
-          mReceivedFirstRTCPPacket(false) {
+          mReceivedFirstRTCPPacket(false),
+          mSeekable(false) {
         mNetLooper->setName("rtsp net");
         mNetLooper->start(false /* runOnCallingThread */,
                           false /* canCallJava */,
@@ -114,9 +116,10 @@
         (new AMessage('abor', id()))->post();
     }
 
-    void seek(int64_t timeUs) {
+    void seek(int64_t timeUs, const sp<AMessage> &doneMsg) {
         sp<AMessage> msg = new AMessage('seek', id());
         msg->setInt64("time", timeUs);
+        msg->setMessage("doneMsg", doneMsg);
         msg->post();
     }
 
@@ -378,6 +381,7 @@
                 mFirstAccessUnitNTP = 0;
                 mNumAccessUnitsReceived = 0;
                 mReceivedFirstRTCPPacket = false;
+                mSeekable = false;
 
                 sp<AMessage> reply = new AMessage('tear', id());
 
@@ -434,6 +438,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 +465,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));
@@ -548,7 +554,17 @@
 
             case 'seek':
             {
+                sp<AMessage> doneMsg;
+                CHECK(msg->findMessage("doneMsg", &doneMsg));
+
                 if (mSeekPending) {
+                    doneMsg->post();
+                    break;
+                }
+
+                if (!mSeekable) {
+                    LOGW("This is a live stream, ignoring seek request.");
+                    doneMsg->post();
                     break;
                 }
 
@@ -557,6 +573,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");
@@ -569,6 +590,7 @@
 
                 sp<AMessage> reply = new AMessage('see1', id());
                 reply->setInt64("time", timeUs);
+                reply->setMessage("doneMsg", doneMsg);
                 mConn->sendRequest(request.c_str(), reply);
                 break;
             }
@@ -597,7 +619,11 @@
 
                 request.append("\r\n");
 
+                sp<AMessage> doneMsg;
+                CHECK(msg->findMessage("doneMsg", &doneMsg));
+
                 sp<AMessage> reply = new AMessage('see2', id());
+                reply->setMessage("doneMsg", doneMsg);
                 mConn->sendRequest(request.c_str(), reply);
                 break;
             }
@@ -612,6 +638,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));
@@ -633,6 +662,11 @@
                 }
 
                 mSeekPending = false;
+
+                sp<AMessage> doneMsg;
+                CHECK(msg->findMessage("doneMsg", &doneMsg));
+
+                doneMsg->post();
                 break;
             }
 
@@ -674,6 +708,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();
@@ -692,6 +737,8 @@
     }
 
     void parsePlayResponse(const sp<ARTSPResponse> &response) {
+        mSeekable = false;
+
         ssize_t i = response->mHeaders.indexOfKey("range");
         if (i < 0) {
             // Server doesn't even tell use what range it is going to
@@ -755,6 +802,8 @@
 
             ++n;
         }
+
+        mSeekable = true;
     }
 
     sp<APacketSource> getPacketSource(size_t index) {
@@ -783,8 +832,10 @@
     uint64_t mFirstAccessUnitNTP;
     int64_t mNumAccessUnitsReceived;
     bool mCheckPending;
+    int32_t mCheckGeneration;
     bool mTryTCPInterleaving;
     bool mReceivedFirstRTCPPacket;
+    bool mSeekable;
 
     struct TrackInfo {
         AString mURL;
diff --git a/native/include/android/looper.h b/native/include/android/looper.h
index a63b744..a9d8426 100644
--- a/native/include/android/looper.h
+++ b/native/include/android/looper.h
@@ -135,6 +135,15 @@
      * to specify this event flag in the requested event set.
      */
     ALOOPER_EVENT_HANGUP = 1 << 3,
+
+    /**
+     * The file descriptor is invalid.
+     * For example, the file descriptor was closed prematurely.
+     *
+     * The looper always sends notifications about invalid file descriptors; it is not necessary
+     * to specify this event flag in the requested event set.
+     */
+    ALOOPER_EVENT_INVALID = 1 << 4,
 };
 
 /**
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/MountService.java b/services/java/com/android/server/MountService.java
index 06f9c41..3b2d836 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -79,8 +79,6 @@
 
     private static final String VOLD_TAG = "VoldConnector";
 
-    protected static final int MAX_OBBS = 8;
-
     /*
      * Internal vold volume state constants
      */
@@ -159,7 +157,6 @@
      * Mounted OBB tracking information. Used to track the current state of all
      * OBBs.
      */
-    final private Map<Integer, Integer> mObbUidUsage = new HashMap<Integer, Integer>();
     final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
     final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
 
@@ -226,7 +223,6 @@
     private static final int OBB_MCS_BOUND = 2;
     private static final int OBB_MCS_UNBIND = 3;
     private static final int OBB_MCS_RECONNECT = 4;
-    private static final int OBB_MCS_GIVE_UP = 5;
 
     /*
      * Default Container Service information
@@ -1591,12 +1587,6 @@
             }
 
             final int callerUid = Binder.getCallingUid();
-
-            final Integer uidUsage = mObbUidUsage.get(callerUid);
-            if (uidUsage != null && uidUsage > MAX_OBBS) {
-                throw new IllegalStateException("Maximum number of OBBs mounted!");
-            }
-
             obbState = new ObbState(filename, token, callerUid);
             addObbState(obbState);
         }
@@ -1695,15 +1685,6 @@
             }
 
             mObbPathToStateMap.put(obbState.filename, obbState);
-
-            // Track the number of OBBs used by this UID.
-            final int uid = obbState.callerUid;
-            final Integer uidUsage = mObbUidUsage.get(uid);
-            if (uidUsage == null) {
-                mObbUidUsage.put(uid, 1);
-            } else {
-                mObbUidUsage.put(uid, uidUsage + 1);
-            }
         }
     }
 
@@ -1721,20 +1702,6 @@
             }
 
             mObbPathToStateMap.remove(obbState.filename);
-
-            // Track the number of OBBs used by this UID.
-            final int uid = obbState.callerUid;
-            final Integer uidUsage = mObbUidUsage.get(uid);
-            if (uidUsage == null) {
-                Slog.e(TAG, "Called removeObbState for UID that isn't in map: " + uid);
-            } else {
-                final int newUsage = uidUsage - 1;
-                if (newUsage == 0) {
-                    mObbUidUsage.remove(uid);
-                } else {
-                    mObbUidUsage.put(uid, newUsage);
-                }
-            }
         }
     }
 
@@ -1747,7 +1714,7 @@
 
     private class ObbActionHandler extends Handler {
         private boolean mBound = false;
-        private List<ObbAction> mActions = new LinkedList<ObbAction>();
+        private final List<ObbAction> mActions = new LinkedList<ObbAction>();
 
         ObbActionHandler(Looper l) {
             super(l);
@@ -1757,7 +1724,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case OBB_RUN_ACTION: {
-                    ObbAction action = (ObbAction) msg.obj;
+                    final ObbAction action = (ObbAction) msg.obj;
 
                     if (DEBUG_OBB)
                         Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
@@ -1793,7 +1760,7 @@
                         }
                         mActions.clear();
                     } else if (mActions.size() > 0) {
-                        ObbAction action = mActions.get(0);
+                        final ObbAction action = mActions.get(0);
                         if (action != null) {
                             action.execute(this);
                         }
@@ -1841,13 +1808,6 @@
                     }
                     break;
                 }
-                case OBB_MCS_GIVE_UP: {
-                    if (DEBUG_OBB)
-                        Slog.i(TAG, "OBB_MCS_GIVE_UP");
-                    mActions.remove(0);
-                    mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
-                    break;
-                }
             }
         }
 
@@ -1887,7 +1847,7 @@
                 mRetries++;
                 if (mRetries > MAX_RETRIES) {
                     Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
-                    mObbActionHandler.sendEmptyMessage(OBB_MCS_GIVE_UP);
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
                     handleError();
                     return;
                 } else {
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 050e0c8..37b4c1d 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;
@@ -3906,17 +3931,22 @@
                     allowed = false;
                 } else if (bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE
                         || bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) {
-                    allowed = (checkSignaturesLP(bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
+                    allowed = (checkSignaturesLP(
+                            bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
                                     == PackageManager.SIGNATURE_MATCH)
                             || (checkSignaturesLP(mPlatformPackage.mSignatures, pkg.mSignatures)
                                     == PackageManager.SIGNATURE_MATCH);
-                    if (bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) {
+                    if (!allowed && bp.protectionLevel
+                            == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) {
                         if (isSystemApp(pkg)) {
                             // For updated system applications, the signatureOrSystem permission
                             // is granted only if it had been defined by the original application.
                             if (isUpdatedSystemApp(pkg)) {
-                                PackageSetting sysPs = mSettings.getDisabledSystemPkg(pkg.packageName);
-                                if(sysPs.grantedPermissions.contains(perm)) {
+                                PackageSetting sysPs = mSettings.getDisabledSystemPkg(
+                                        pkg.packageName);
+                                final GrantedPermissions origGp = sysPs.sharedUser != null
+                                        ? sysPs.sharedUser : sysPs;
+                                if (origGp.grantedPermissions.contains(perm)) {
                                     allowed = true;
                                 } else {
                                     allowed = false;
@@ -4467,7 +4497,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 +5041,6 @@
                 try { if (out != null) out.close(); } catch (IOException e) {}
             }
 
-            if (!temp) {
-                NativeLibraryHelper.copyNativeBinariesLI(codeFile, new File(libraryPath));
-            }
-
             return ret;
         }
 
@@ -5460,7 +5487,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 +5551,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 +5606,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 +5660,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 +5683,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 +5700,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 +6234,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 +7087,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 +7163,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 +7679,8 @@
         String nativeLibraryPathString;
         String obbPathString;
         long timeStamp;
+        long firstInstallTime;
+        long lastUpdateTime;
         int versionCode;
 
         boolean uidError;
@@ -7696,6 +7745,8 @@
             gids = base.gids;
 
             timeStamp = base.timeStamp;
+            firstInstallTime = base.firstInstallTime;
+            lastUpdateTime = base.lastUpdateTime;
             signatures = base.signatures;
             permissionsFixed = base.permissionsFixed;
             haveGids = base.haveGids;
@@ -8565,7 +8616,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 +8670,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 +9028,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 +9104,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 +9145,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 +9204,8 @@
                                 + parser.getPositionDescription());
                     } else {
                         packageSetting.setTimeStamp(timeStamp);
+                        packageSetting.firstInstallTime = firstInstallTime;
+                        packageSetting.lastUpdateTime = lastUpdateTime;
                     }
                 } else if (sharedIdStr != null) {
                     userId = sharedIdStr != null
@@ -9111,6 +9215,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 +9775,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 +10000,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 +10014,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/WifiService.java b/services/java/com/android/server/WifiService.java
index 3d95bf0..a63b3d8 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -2163,12 +2163,12 @@
             if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
             return false;
         }
-        if (ws != null) {
-            enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
-        }
         if (ws != null && ws.size() == 0) {
             ws = null;
         }
+        if (ws != null) {
+            enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
+        }
         if (ws == null) {
             ws = new WorkSource(Binder.getCallingUid());
         }
@@ -2227,17 +2227,18 @@
                 ++mScanLocksAcquired;
                 break;
             }
+
+            // Be aggressive about adding new locks into the accounted state...
+            // we want to over-report rather than under-report.
+            reportStartWorkSource();
+
+            updateWifiState();
+            return true;
         } catch (RemoteException e) {
+            return false;
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-
-        // Be aggressive about adding new locks into the accounted state...
-        // we want to over-report rather than under-report.
-        reportStartWorkSource();
-
-        updateWifiState();
-        return true;
     }
 
     public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
@@ -2283,9 +2284,9 @@
 
         hadLock = (wifiLock != null);
 
-        if (hadLock) {
-            long ident = Binder.clearCallingIdentity();
-            try {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (hadLock) {
                 noteAcquireWifiLock(wifiLock);
                 switch(wifiLock.mMode) {
                     case WifiManager.WIFI_MODE_FULL:
@@ -2298,13 +2299,16 @@
                         ++mScanLocksReleased;
                         break;
                 }
-            } catch (RemoteException e) {
-            } finally {
-                Binder.restoreCallingIdentity(ident);
             }
+
+            // TODO - should this only happen if you hadLock?
+            updateWifiState();
+
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
-        // TODO - should this only happen if you hadLock?
-        updateWifiState();
+
         return hadLock;
     }
 
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/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index e9eb4f0..87271e7 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -1228,6 +1228,7 @@
     private void reportAGpsStatus(int type, int status) {
         switch (status) {
             case GPS_REQUEST_AGPS_DATA_CONN:
+                if (DEBUG) Log.d(TAG, "GPS_REQUEST_AGPS_DATA_CONN");
                 // Set mAGpsDataConnectionState before calling startUsingNetworkFeature
                 //  to avoid a race condition with handleUpdateNetworkState()
                 mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
@@ -1250,6 +1251,7 @@
                 }
                 break;
             case GPS_RELEASE_AGPS_DATA_CONN:
+                if (DEBUG) Log.d(TAG, "GPS_RELEASE_AGPS_DATA_CONN");
                 if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
                     mConnMgr.stopUsingNetworkFeature(
                             ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
@@ -1258,13 +1260,13 @@
                 }
                 break;
             case GPS_AGPS_DATA_CONNECTED:
-                // Log.d(TAG, "GPS_AGPS_DATA_CONNECTED");
+                if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONNECTED");
                 break;
             case GPS_AGPS_DATA_CONN_DONE:
-                // Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE");
+                if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE");
                 break;
             case GPS_AGPS_DATA_CONN_FAILED:
-                // Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED");
+                if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED");
                 break;
         }
     }
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/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index 71c7aba..b9ceaa1 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -238,15 +238,25 @@
     return interface;
 }
 
+static const GpsInterface* GetGpsInterface() {
+    if (!sGpsInterface) {
+        sGpsInterface = get_gps_interface();
+        if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0) {
+            sGpsInterface = NULL;
+            return NULL;
+        }
+    }
+    return sGpsInterface;
+}
+
 static const AGpsInterface* GetAGpsInterface()
 {
-    if (!sGpsInterface)
-        sGpsInterface = get_gps_interface();
-    if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+    const GpsInterface* interface = GetGpsInterface();
+    if (!interface)
         return NULL;
 
     if (!sAGpsInterface) {
-        sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+        sAGpsInterface = (const AGpsInterface*)interface->get_extension(AGPS_INTERFACE);
         if (sAGpsInterface)
             sAGpsInterface->init(&sAGpsCallbacks);
     }
@@ -255,13 +265,12 @@
 
 static const GpsNiInterface* GetNiInterface()
 {
-    if (!sGpsInterface)
-        sGpsInterface = get_gps_interface();
-    if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+    const GpsInterface* interface = GetGpsInterface();
+    if (!interface)
         return NULL;
 
     if (!sGpsNiInterface) {
-       sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+       sGpsNiInterface = (const GpsNiInterface*)interface->get_extension(GPS_NI_INTERFACE);
         if (sGpsNiInterface)
            sGpsNiInterface->init(&sGpsNiCallbacks);
     }
@@ -270,13 +279,12 @@
 
 static const AGpsRilInterface* GetAGpsRilInterface()
 {
-    if (!sGpsInterface)
-        sGpsInterface = get_gps_interface();
-    if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+    const GpsInterface* interface = GetGpsInterface();
+    if (!interface)
         return NULL;
 
     if (!sAGpsRilInterface) {
-       sAGpsRilInterface = (const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);
+       sAGpsRilInterface = (const AGpsRilInterface*)interface->get_extension(AGPS_RIL_INTERFACE);
         if (sAGpsRilInterface)
             sAGpsRilInterface->init(&sAGpsRilCallbacks);
     }
@@ -297,9 +305,7 @@
 }
 
 static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) {
-    if (!sGpsInterface)
-        sGpsInterface = get_gps_interface();
-    return (sGpsInterface != NULL);
+    return (sGpsInterface != NULL || get_gps_interface() != NULL);
 }
 
 static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)
@@ -308,13 +314,12 @@
     if (!mCallbacksObj)
         mCallbacksObj = env->NewGlobalRef(obj);
 
-    if (!sGpsInterface)
-        sGpsInterface = get_gps_interface();
-    if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+    const GpsInterface* interface = GetGpsInterface();
+    if (!interface)
         return false;
 
     if (!sGpsDebugInterface)
-       sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
+       sGpsDebugInterface = (const GpsDebugInterface*)interface->get_extension(GPS_DEBUG_INTERFACE);
 
     return true;
 }
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 7a026fa..b09df82 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -925,7 +925,7 @@
         }
 
         if (hasActiveFgCall()) {
-            getActiveFgCall().getPhone().sendDtmf(c);
+            getActiveFgCall().getPhone().startDtmf(c);
             result = true;
         }
 
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 90ecbd7..6ddb312 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -358,8 +358,14 @@
                             EVENT_SIM_RECORDS_LOADED, null);
                     mNeedToRegForSimLoaded = false;
                 }
-                // restore the previous network selection.
-                phone.restoreSavedNetworkSelection(null);
+
+                boolean skipRestoringSelection = phone.getContext().getResources().getBoolean(
+                        com.android.internal.R.bool.skip_restoring_network_selection);
+
+                if (!skipRestoringSelection) {
+                    // restore the previous network selection.
+                    phone.restoreSavedNetworkSelection(null);
+                }
                 pollState();
                 // Signal strength polling stops when radio is off
                 queueNextSignalStrengthPoll();
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..ee554b5 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -92,6 +92,7 @@
             new HashMap<String, ISipSession>();
 
     private ConnectivityReceiver mConnectivityReceiver;
+    private boolean mScreenOn;
 
     /**
      * Starts the SIP service. Do nothing if the SIP API is not supported on the
@@ -111,11 +112,27 @@
         mConnectivityReceiver = new ConnectivityReceiver();
         context.registerReceiver(mConnectivityReceiver,
                 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+        context.registerReceiver(mScreenOnOffReceiver,
+                new IntentFilter(Intent.ACTION_SCREEN_ON));
+        context.registerReceiver(mScreenOnOffReceiver,
+                new IntentFilter(Intent.ACTION_SCREEN_OFF));
 
         mTimer = new WakeupTimer(context);
         mWifiOnly = SipManager.isSipWifiOnly(context);
     }
 
+    BroadcastReceiver mScreenOnOffReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mScreenOn = true;
+            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mScreenOn = false;
+            }
+        }
+    };
+
     private MyExecutor getExecutor() {
         // create mExecutor lazily
         if (mExecutor == null) mExecutor = new MyExecutor();
@@ -123,6 +140,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 +153,8 @@
     }
 
     public void open(SipProfile localProfile) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.USE_SIP, null);
         localProfile.setCallingUid(Binder.getCallingUid());
         try {
             createGroup(localProfile);
@@ -146,6 +167,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 +182,7 @@
                     incomingCallPendingIntent, listener);
             if (localProfile.getAutoRegistration()) {
                 group.openToReceiveCalls();
-                if (isWifiOn()) grabWifiLock();
+                if (isWifiActive()) grabWifiLock();
             }
         } catch (SipException e) {
             Log.e(TAG, "openToReceiveCalls()", e);
@@ -181,6 +204,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 +216,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 +233,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 +247,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 +260,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 +274,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 +365,8 @@
         }
     }
 
-    private boolean isWifiOn() {
+    private boolean isWifiActive() {
         return "WIFI".equalsIgnoreCase(mNetworkType);
-        //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType));
     }
 
     private synchronized void onConnectivityChanged(
@@ -349,7 +383,10 @@
         boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType);
         boolean wifiOn = isWifi && connected;
         if (wifiOff) {
-            releaseWifiLock();
+            if (mScreenOn) releaseWifiLock();
+            // If the screen is off, we still keep the wifi lock in order
+            // to be able to reassociate with any available AP. Otherwise,
+            // the wifi driver could be stopped after 15 mins of idle time.
         } else if (wifiOn) {
             if (anyOpened()) grabWifiLock();
         }
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.