Merge "fix a bug where timeouts would only be handled when a message was delivered" 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=""android.permission.USE_SIP""
+ 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=""android:sipCallID""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_OFFER_SD"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android:sipOfferSD""
+ 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/Activity.java b/core/java/android/app/Activity.java
index 2c4babb..78a77eb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2609,6 +2609,10 @@
* <p>This can be useful if you know that you will never show a dialog again and
* want to avoid the overhead of saving and restoring it in the future.
*
+ * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, this function
+ * will not throw an exception if you try to remove an ID that does not
+ * currently have an associated dialog.</p>
+ *
* @param id The id of the managed dialog.
*
* @see #onCreateDialog(int, Bundle)
@@ -2617,17 +2621,13 @@
* @see #dismissDialog(int)
*/
public final void removeDialog(int id) {
- if (mManagedDialogs == null) {
- return;
+ if (mManagedDialogs != null) {
+ final ManagedDialog md = mManagedDialogs.get(id);
+ if (md != null) {
+ md.mDialog.dismiss();
+ mManagedDialogs.remove(id);
+ }
}
-
- final ManagedDialog md = mManagedDialogs.get(id);
- if (md == null) {
- return;
- }
-
- md.mDialog.dismiss();
- mManagedDialogs.remove(id);
}
/**
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..00d8ae3 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -28,6 +28,7 @@
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.Downloads;
+import android.util.Log;
import android.util.Pair;
import java.io.File;
@@ -53,6 +54,8 @@
* download in a notification or from the downloads UI.
*/
public class DownloadManager {
+ private static final String TAG = "DownloadManager";
+
/**
* An identifier for a particular download, unique across the system. Clients use this ID to
* make subsequent calls related to the download.
@@ -567,18 +570,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 +642,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 +681,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 +743,19 @@
}
/**
- * 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) {
+ if (ids == null || ids.length == 0) {
+ // called with nothing to remove!
+ throw new IllegalArgumentException("input param 'ids' can't be null");
}
+ return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
}
/**
@@ -776,20 +783,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 +807,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 +818,34 @@
}
/**
+ * Get a parameterized SQL WHERE clause to select a bunch of IDs.
+ */
+ static String getWhereClauseForIds(long[] ids) {
+ StringBuilder whereClause = new StringBuilder();
+ whereClause.append("(");
+ for (int i = 0; i < ids.length; i++) {
+ if (i > 0) {
+ whereClause.append("OR ");
+ }
+ whereClause.append(Downloads.Impl._ID);
+ 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/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index 1484204..3221afa 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -186,9 +186,21 @@
* {@link #commit} will block until all async commits are
* completed as well as the commit itself.
*
- * <p>If you call this from an {@link android.app.Activity},
- * the base class will wait for any async commits to finish in
- * its {@link android.app.Activity#onPause}.</p>
+ * <p>As {@link SharedPreferences} instances are singletons within
+ * a process, it's safe to replace any instance of {@link #commit} with
+ * {@link #apply} if you were already ignoring the return value.
+ *
+ * <p>You don't need to worry about Android component
+ * lifecycles and their interaction with <code>apply()</code>
+ * writing to disk. The framework makes sure in-flight disk
+ * writes from <code>apply()</code> complete before switching
+ * states.
+ *
+ * <p class='note'>The SharedPreferences.Editor interface
+ * isn't expected to be implemented directly. However, if you
+ * previously did implement it and are now getting errors
+ * about missing <code>apply()</code>, you can simply call
+ * {@link #commit} from <code>apply()</code>.
*/
void apply();
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 6413cec..f9a1637 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -95,7 +95,7 @@
public static final long NOT_IN_BACKOFF_MODE = -1;
- private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
+ public static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
// TODO: i18n -- grab these out of resources.
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/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 7b930d5..a27ba84 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1354,8 +1354,23 @@
/**
* Sets the dimensions for preview pictures.
*
+ * The sides of width and height are based on camera orientation. That
+ * is, the preview size is the size before it is rotated by display
+ * orientation. So applications need to consider the display orientation
+ * while setting preview size. For example, suppose the camera supports
+ * both 480x320 and 320x480 preview sizes. The application wants a 3:2
+ * preview ratio. If the display orientation is set to 0 or 180, preview
+ * size should be set to 480x320. If the display orientation is set to
+ * 90 or 270, preview size should be set to 320x480. The display
+ * orientation should also be considered while setting picture size and
+ * thumbnail size.
+ *
* @param width the width of the pictures, in pixels
* @param height the height of the pictures, in pixels
+ * @see #setDisplayOrientation(int)
+ * @see #getCameraInfo(int, CameraInfo)
+ * @see #setPictureSize(int, int)
+ * @see #setJpegThumbnailSize(int, int)
*/
public void setPreviewSize(int width, int height) {
String v = Integer.toString(width) + "x" + Integer.toString(height);
@@ -1389,8 +1404,12 @@
* applications set both width and height to 0, EXIF will not contain
* thumbnail.
*
+ * Applications need to consider the display orientation. See {@link
+ * #setPreviewSize(int,int)} for reference.
+ *
* @param width the width of the thumbnail, in pixels
* @param height the height of the thumbnail, in pixels
+ * @see #setPreviewSize(int,int)
*/
public void setJpegThumbnailSize(int width, int height) {
set(KEY_JPEG_THUMBNAIL_WIDTH, width);
@@ -1606,8 +1625,13 @@
/**
* Sets the dimensions for pictures.
*
+ * Applications need to consider the display orientation. See {@link
+ * #setPreviewSize(int,int)} for reference.
+ *
* @param width the width for pictures, in pixels
* @param height the height for pictures, in pixels
+ * @see #setPreviewSize(int,int)
+ *
*/
public void setPictureSize(int width, int height) {
String v = Integer.toString(width) + "x" + Integer.toString(height);
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/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 9494a06..c185007 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -104,6 +104,10 @@
// Only show an annoying dialog at most every 30 seconds
private static final long MIN_DIALOG_INTERVAL_MS = 30000;
+ // How many offending stacks to keep track of (and time) per loop
+ // of the Looper.
+ private static final int MAX_OFFENSES_PER_LOOP = 10;
+
// Thread-policy:
/**
@@ -680,6 +684,17 @@
}
}
+ private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
+ new ThreadLocal<ArrayList<ViolationInfo>>() {
+ @Override protected ArrayList<ViolationInfo> initialValue() {
+ return new ArrayList<ViolationInfo>();
+ }
+ };
+
+ private static boolean tooManyViolationsThisLoop() {
+ return violationsBeingTimed.get().size() >= MAX_OFFENSES_PER_LOOP;
+ }
+
private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
private int mPolicyMask;
@@ -707,6 +722,9 @@
if ((mPolicyMask & DETECT_DISK_WRITE) == 0) {
return;
}
+ if (tooManyViolationsThisLoop()) {
+ return;
+ }
BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
e.fillInStackTrace();
startHandlingViolationException(e);
@@ -717,6 +735,9 @@
if ((mPolicyMask & DETECT_DISK_READ) == 0) {
return;
}
+ if (tooManyViolationsThisLoop()) {
+ return;
+ }
BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
e.fillInStackTrace();
startHandlingViolationException(e);
@@ -727,6 +748,9 @@
if ((mPolicyMask & DETECT_NETWORK) == 0) {
return;
}
+ if (tooManyViolationsThisLoop()) {
+ return;
+ }
BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
e.fillInStackTrace();
startHandlingViolationException(e);
@@ -747,13 +771,6 @@
handleViolationWithTimingAttempt(info);
}
- private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
- new ThreadLocal<ArrayList<ViolationInfo>>() {
- @Override protected ArrayList<ViolationInfo> initialValue() {
- return new ArrayList<ViolationInfo>();
- }
- };
-
// Attempts to fill in the provided ViolationInfo's
// durationMillis field if this thread has a Looper we can use
// to measure with. We measure from the time of violation
@@ -780,7 +797,7 @@
MessageQueue queue = Looper.myQueue();
final ArrayList<ViolationInfo> records = violationsBeingTimed.get();
- if (records.size() >= 10) {
+ if (records.size() >= MAX_OFFENSES_PER_LOOP) {
// Not worth measuring. Too many offenses in one loop.
return;
}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index dde6493..36d676a 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -1195,7 +1195,14 @@
private void tryCommit(SharedPreferences.Editor editor) {
if (mPreferenceManager.shouldCommit()) {
- editor.apply();
+ try {
+ editor.apply();
+ } catch (AbstractMethodError unused) {
+ // The app injected its own pre-Gingerbread
+ // SharedPreferences.Editor implementation without
+ // an apply method.
+ editor.commit();
+ }
}
}
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index fa83897..e44a090 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -443,7 +443,16 @@
pm.setSharedPreferencesMode(sharedPreferencesMode);
pm.inflateFromResource(context, resId, null);
- defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).apply();
+ SharedPreferences.Editor editor =
+ defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
+ try {
+ editor.apply();
+ } catch (AbstractMethodError unused) {
+ // The app injected its own pre-Gingerbread
+ // SharedPreferences.Editor implementation without
+ // an apply method.
+ editor.commit();
+ }
}
}
@@ -478,15 +487,21 @@
boolean shouldCommit() {
return !mNoCommit;
}
-
+
private void setNoCommit(boolean noCommit) {
if (!noCommit && mEditor != null) {
- mEditor.apply();
+ try {
+ mEditor.apply();
+ } catch (AbstractMethodError unused) {
+ // The app injected its own pre-Gingerbread
+ // SharedPreferences.Editor implementation without
+ // an apply method.
+ mEditor.commit();
+ }
}
-
mNoCommit = noCommit;
}
-
+
/**
* Returns the activity that shows the preferences. This is useful for doing
* managed queries, but in most cases the use of {@link #getContext()} is
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 43c957a..9e800df 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -28,8 +28,21 @@
private static final boolean DEBUG = false;
+ /**
+ * Interface to receive notification of when an InputQueue is associated
+ * and dissociated with a thread.
+ */
public static interface Callback {
+ /**
+ * Called when the given InputQueue is now associated with the
+ * thread making this call, so it can start receiving events from it.
+ */
void onInputQueueCreated(InputQueue queue);
+
+ /**
+ * Called when the given InputQueue is no longer associated with
+ * the thread and thus not dispatching events.
+ */
void onInputQueueDestroyed(InputQueue queue);
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 9e7eedf..0e5ece1 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -46,10 +46,12 @@
* with the special action {@link #ACTION_MULTIPLE} that either specifies
* that single repeated key code or a sequence of characters to insert.
* </p><p>
- * In general, the framework makes no guarantees that the key events delivered
- * to a view constitute a complete key press. In particular, there is no
- * guarantee that a view will always receive a key event with {@link #ACTION_UP}
- * for each {@link #ACTION_DOWN} that was delivered.
+ * In general, the framework cannot guarantee that the key events it delivers
+ * to a view always constitute complete key sequences since some events may be dropped
+ * or modified by containing views before they are delivered. The view implementation
+ * should be prepared to handle {@link #FLAG_CANCELED} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior key press.
* </p><p>
* Refer to {@link InputDevice} for more information about how different kinds of
* input devices and sources represent keys and buttons.
@@ -195,7 +197,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 +208,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 +231,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/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6705596..c2fec96 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -81,10 +81,12 @@
* }
* }
* </code></pre></p><p>
- * In general, the framework makes no guarantees that the motion events delivered
- * to a view constitute a complete gesture. In particular, there is no
- * guarantee that a view will always receive a motion event with {@link #ACTION_UP}
- * for each {@link #ACTION_DOWN} that was delivered.
+ * In general, the framework cannot guarantee that the motion events it delivers
+ * to a view always constitute a complete motion sequences since some events may be dropped
+ * or modified by containing views before they are delivered. The view implementation
+ * should be prepared to handle {@link #ACTION_CANCEL} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior gesture.
* </p><p>
* Refer to {@link InputDevice} for more information about how different kinds of
* input devices and sources represent pointer coordinates.
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 954b3e7..ef7716e 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -65,6 +65,7 @@
* @hide
*/
public interface WindowManagerPolicy {
+ // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h.
public final static int FLAG_WAKE = 0x00000001;
public final static int FLAG_WAKE_DROPPED = 0x00000002;
public final static int FLAG_SHIFT = 0x00000004;
@@ -79,6 +80,7 @@
public final static int FLAG_WOKE_HERE = 0x10000000;
public final static int FLAG_BRIGHT_HERE = 0x20000000;
+ public final static int FLAG_PASS_TO_USER = 0x40000000;
public final static boolean WATCH_POINTER = false;
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/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index aa14c81..d3aa42f 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -17,14 +17,14 @@
package android.widget;
import com.android.internal.R;
+
import android.content.Context;
-import android.content.res.Resources;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
@@ -329,11 +329,6 @@
TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
- if (dangerous) {
- final Resources resources = context.getResources();
- permGrpView.setTextColor(resources.getColor(R.color.perms_dangerous_grp_color));
- permDescView.setTextColor(resources.getColor(R.color.perms_dangerous_perm_color));
- }
ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon);
imgView.setImageDrawable(icon);
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 3ac179c..5be52c4 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;
@@ -287,9 +288,13 @@
}
InputMethodState mInputMethodState;
- private int mTextSelectHandleLeftRes;
- private int mTextSelectHandleRightRes;
- private int mTextSelectHandleRes;
+ int mTextSelectHandleLeftRes;
+ int mTextSelectHandleRightRes;
+ int mTextSelectHandleRes;
+
+ Drawable mSelectHandleLeft;
+ Drawable mSelectHandleRight;
+ Drawable mSelectHandleCenter;
/*
* Kick-start the font cache for the zygote process (to pay the cost of
@@ -4316,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) {
@@ -4348,6 +4354,7 @@
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
+ mDPadCenterIsDown = true;
if (shouldAdvanceFocusOnEnter()) {
return 0;
}
@@ -4442,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.
@@ -4462,6 +4470,7 @@
return super.onKeyUp(keyCode, event);
case KeyEvent.KEYCODE_ENTER:
+ mEnterKeyIsDown = false;
if (mInputContentType != null
&& mInputContentType.onEditorActionListener != null
&& mInputContentType.enterDown) {
@@ -6540,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) {
@@ -6605,7 +6614,9 @@
terminateTextSelectionMode();
}
- mLastTouchOffset = -1;
+ if (mSelectionModifierCursorController != null) {
+ ((SelectionModifierCursorController) mSelectionModifierCursorController).resetTouchOffsets();
+ }
}
startStopMarquee(focused);
@@ -6617,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);
@@ -6639,10 +6668,7 @@
if (mInputContentType != null) {
mInputContentType.enterDown = false;
}
- hideInsertionPointCursorController();
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.hide();
- }
+ hideControllers();
}
startStopMarquee(hasWindowFocus);
@@ -6652,10 +6678,7 @@
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility != VISIBLE) {
- hideInsertionPointCursorController();
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.hide();
- }
+ hideControllers();
}
}
@@ -6708,7 +6731,7 @@
// Tapping outside stops selection mode, if any
stopTextSelectionMode();
- if (mInsertionPointCursorController != null) {
+ if (mInsertionPointCursorController != null && mText.length() > 0) {
mInsertionPointCursorController.show();
}
}
@@ -7222,9 +7245,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
@@ -7232,24 +7267,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());
}
@@ -7258,14 +7300,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;
@@ -7312,6 +7351,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();
@@ -7337,21 +7384,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()) {
@@ -7480,43 +7512,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();
@@ -7539,7 +7537,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);
@@ -7552,6 +7549,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()) {
@@ -7597,10 +7637,7 @@
private void stopTextSelectionMode() {
if (mIsInTextSelectionMode) {
Selection.setSelection((Spannable) mText, getSelectionEnd());
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.hide();
- }
-
+ hideSelectionModifierCursorController();
mIsInTextSelectionMode = false;
}
}
@@ -7651,26 +7688,75 @@
private int mPositionY;
private CursorController mController;
private boolean mIsDragging;
- private float mOffsetX;
- private float mOffsetY;
+ private float mTouchToWindowOffsetX;
+ private float mTouchToWindowOffsetY;
private float mHotspotX;
private float mHotspotY;
+ private int mHeight;
+ private float mTouchOffsetY;
private int mLastParentX;
private int mLastParentY;
- public HandleView(CursorController controller, Drawable handle) {
+ public static final int LEFT = 0;
+ public static final int CENTER = 1;
+ public static final int RIGHT = 2;
+
+ public HandleView(CursorController controller, int pos) {
super(TextView.this.mContext);
mController = controller;
- mDrawable = handle;
mContainer = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
mContainer.setClippingEnabled(false);
+ mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
- final int handleWidth = mDrawable.getIntrinsicWidth();
+ setOrientation(pos);
+ }
+
+ public void setOrientation(int pos) {
+ int handleWidth;
+ switch (pos) {
+ case LEFT: {
+ if (mSelectHandleLeft == null) {
+ mSelectHandleLeft = mContext.getResources().getDrawable(
+ mTextSelectHandleLeftRes);
+ }
+ mDrawable = mSelectHandleLeft;
+ handleWidth = mDrawable.getIntrinsicWidth();
+ mHotspotX = handleWidth / 4 * 3;
+ break;
+ }
+
+ case RIGHT: {
+ if (mSelectHandleRight == null) {
+ mSelectHandleRight = mContext.getResources().getDrawable(
+ mTextSelectHandleRightRes);
+ }
+ mDrawable = mSelectHandleRight;
+ handleWidth = mDrawable.getIntrinsicWidth();
+ mHotspotX = handleWidth / 4;
+ break;
+ }
+
+ case CENTER:
+ default: {
+ if (mSelectHandleCenter == null) {
+ mSelectHandleCenter = mContext.getResources().getDrawable(
+ mTextSelectHandleRes);
+ }
+ mDrawable = mSelectHandleCenter;
+ handleWidth = mDrawable.getIntrinsicWidth();
+ mHotspotX = handleWidth / 2;
+ break;
+ }
+ }
+
final int handleHeight = mDrawable.getIntrinsicHeight();
- mHotspotX = handleWidth * 0.5f;
- mHotspotY = -handleHeight * 0.2f;
+
+ mTouchOffsetY = -handleHeight * 0.3f;
+ mHotspotY = 0;
+ mHeight = handleHeight;
+ invalidate();
}
@Override
@@ -7735,10 +7821,10 @@
final int[] coords = mTempCoords;
hostView.getLocationInWindow(coords);
final int posX = coords[0] + mPositionX + (int) mHotspotX;
- final int posY = coords[1] + mPositionY;
+ final int posY = coords[1] + mPositionY + (int) mHotspotY;
return posX >= clip.left && posX <= clip.right &&
- posY >= clip.top && posY + mHotspotY <= clip.bottom;
+ posY >= clip.top && posY <= clip.bottom;
}
private void moveTo(int x, int y) {
@@ -7761,8 +7847,8 @@
TextView.this.getLocationInWindow(coords);
}
if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
- mOffsetX += coords[0] - mLastParentX;
- mOffsetY += coords[1] - mLastParentY;
+ mTouchToWindowOffsetX += coords[0] - mLastParentX;
+ mTouchToWindowOffsetY += coords[1] - mLastParentY;
mLastParentX = coords[0];
mLastParentY = coords[1];
}
@@ -7791,8 +7877,8 @@
case MotionEvent.ACTION_DOWN: {
final float rawX = ev.getRawX();
final float rawY = ev.getRawY();
- mOffsetX = rawX - mPositionX;
- mOffsetY = rawY - mPositionY;
+ mTouchToWindowOffsetX = rawX - mPositionX;
+ mTouchToWindowOffsetY = rawY - mPositionY;
final int[] coords = mTempCoords;
TextView.this.getLocationInWindow(coords);
mLastParentX = coords[0];
@@ -7803,11 +7889,10 @@
case MotionEvent.ACTION_MOVE: {
final float rawX = ev.getRawX();
final float rawY = ev.getRawY();
- final float newPosX = rawX - mOffsetX + mHotspotX;
- final float newPosY = rawY - mOffsetY + mHotspotY;
+ 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;
}
@@ -7830,9 +7915,9 @@
final int lineBottom = mLayout.getLineBottom(line);
final Rect bounds = sCursorControllerTempRect;
- bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0)
+ bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - mHotspotX)
+ TextView.this.mScrollX;
- bounds.top = (bottom ? lineBottom : lineTop) + TextView.this.mScrollY;
+ bounds.top = (bottom ? lineBottom : lineTop - mHeight) + TextView.this.mScrollY;
bounds.right = bounds.left + width;
bounds.bottom = bounds.top + height;
@@ -7855,8 +7940,7 @@
};
InsertionPointCursorController() {
- Resources res = mContext.getResources();
- mHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRes));
+ mHandle = new HandleView(this, HandleView.CENTER);
}
public void show() {
@@ -7931,9 +8015,9 @@
};
SelectionModifierCursorController() {
- Resources res = mContext.getResources();
- mStartHandle = new HandleView(this, res.getDrawable(mTextSelectHandleLeftRes));
- mEndHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRightRes));
+ mStartHandle = new HandleView(this, HandleView.LEFT);
+ mEndHandle = new HandleView(this, HandleView.RIGHT);
+ resetTouchOffsets();
}
public void show() {
@@ -7974,31 +8058,25 @@
// Handle the case where start and end are swapped, making sure start <= end
if (handle == mStartHandle) {
- if (offset <= selectionEnd) {
- if (selectionStart == offset) {
- return; // no change, no need to redraw;
- }
- selectionStart = offset;
- } else {
- selectionStart = selectionEnd;
- selectionEnd = offset;
- HandleView temp = mStartHandle;
- mStartHandle = mEndHandle;
- mEndHandle = temp;
+ if (selectionStart == offset || offset > selectionEnd) {
+ return; // no change, no need to redraw;
}
+ // If the user "closes" the selection entirely they were probably trying to
+ // select a single character. Help them out.
+ if (offset == selectionEnd) {
+ offset = selectionEnd - 1;
+ }
+ selectionStart = offset;
} else {
- if (offset >= selectionStart) {
- if (selectionEnd == offset) {
- return; // no change, no need to redraw;
- }
- selectionEnd = offset;
- } else {
- selectionEnd = selectionStart;
- selectionStart = offset;
- HandleView temp = mStartHandle;
- mStartHandle = mEndHandle;
- mEndHandle = temp;
+ if (selectionEnd == offset || offset < selectionStart) {
+ return; // no change, no need to redraw;
}
+ // If the user "closes" the selection entirely they were probably trying to
+ // select a single character. Help them out.
+ if (offset == selectionStart) {
+ offset = selectionStart + 1;
+ }
+ selectionEnd = offset;
}
Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
@@ -8016,22 +8094,22 @@
return;
}
- boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) ==
- mLayout.getLineForOffset(selectionEnd);
- mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
+ mStartHandle.positionAtCursor(selectionStart, true);
mEndHandle.positionAtCursor(selectionEnd, true);
hideDelayed(DELAY_BEFORE_FADE_OUT);
}
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;
@@ -8071,6 +8149,10 @@
return mMaxTouchOffset;
}
+ public void resetTouchOffsets() {
+ mMinTouchOffset = mMaxTouchOffset = -1;
+ }
+
/**
* @return true iff this controller is currently used to move the selection start.
*/
@@ -8091,9 +8173,15 @@
}
}
+ private void hideSelectionModifierCursorController() {
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.hide();
+ }
+ }
+
private void hideControllers() {
hideInsertionPointCursorController();
- stopTextSelectionMode();
+ hideSelectionModifierCursorController();
}
private int getOffsetForHorizontal(int line, int x) {
@@ -8144,7 +8232,7 @@
final int previousLine = layout.getLineForOffset(previousOffset);
final int previousLineTop = layout.getLineTop(previousLine);
final int previousLineBottom = layout.getLineBottom(previousLine);
- final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 6;
+ final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 8;
// If new line is just before or after previous line and y position is less than
// hysteresisThreshold away from previous line, keep cursor on previous line.
@@ -8188,7 +8276,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/btn_code_lock_touched.png b/core/res/res/drawable-hdpi/btn_code_lock_touched.png
index b90508c..0410dd3 100644
--- a/core/res/res/drawable-hdpi/btn_code_lock_touched.png
+++ b/core/res/res/drawable-hdpi/btn_code_lock_touched.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_media_player.9.png b/core/res/res/drawable-hdpi/btn_media_player.9.png
index bf16315..c23cfb6e 100644
--- a/core/res/res/drawable-hdpi/btn_media_player.9.png
+++ b/core/res/res/drawable-hdpi/btn_media_player.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_media_player_disabled.9.png b/core/res/res/drawable-hdpi/btn_media_player_disabled.9.png
index d7b8ed5..45228f1 100644
--- a/core/res/res/drawable-hdpi/btn_media_player_disabled.9.png
+++ b/core/res/res/drawable-hdpi/btn_media_player_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_media_player_disabled_selected.9.png b/core/res/res/drawable-hdpi/btn_media_player_disabled_selected.9.png
index 1a35c31..27e4e1d 100644
--- a/core/res/res/drawable-hdpi/btn_media_player_disabled_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_media_player_disabled_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_media_player_pressed.9.png b/core/res/res/drawable-hdpi/btn_media_player_pressed.9.png
index 17dd3fc..0b98507 100644
--- a/core/res/res/drawable-hdpi/btn_media_player_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_media_player_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_media_player_selected.9.png b/core/res/res/drawable-hdpi/btn_media_player_selected.9.png
index a146d8f..e43fa8a 100644
--- a/core/res/res/drawable-hdpi/btn_media_player_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_media_player_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_off_background.png b/core/res/res/drawable-hdpi/checkbox_off_background.png
index a8e4785..275cba6 100644
--- a/core/res/res/drawable-hdpi/checkbox_off_background.png
+++ b/core/res/res/drawable-hdpi/checkbox_off_background.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/checkbox_on_background.png b/core/res/res/drawable-hdpi/checkbox_on_background.png
index 800d3d5..a390814 100644
--- a/core/res/res/drawable-hdpi/checkbox_on_background.png
+++ b/core/res/res/drawable-hdpi/checkbox_on_background.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_bullet_key_permission.png b/core/res/res/drawable-hdpi/ic_bullet_key_permission.png
index 98d95dc..b6b840a 100644
--- a/core/res/res/drawable-hdpi/ic_bullet_key_permission.png
+++ b/core/res/res/drawable-hdpi/ic_bullet_key_permission.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
index 467a013..c45b956 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
index c21b24e..4f95d50 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
index 87007a3..10a37b7 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
index 109be42..b9ec237 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
index 030c9e9..30fcda5 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_normal.png b/core/res/res/drawable-hdpi/jog_tab_left_normal.png
old mode 100755
new mode 100644
index 8b89538..d43c5e2
--- a/core/res/res/drawable-hdpi/jog_tab_left_normal.png
+++ b/core/res/res/drawable-hdpi/jog_tab_left_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_normal.png b/core/res/res/drawable-hdpi/jog_tab_right_normal.png
old mode 100755
new mode 100644
index 01bba0b..1eb4234
--- a/core/res/res/drawable-hdpi/jog_tab_right_normal.png
+++ b/core/res/res/drawable-hdpi/jog_tab_right_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/panel_background.9.png b/core/res/res/drawable-hdpi/panel_background.9.png
index bfe5713..03175d4 100644
--- a/core/res/res/drawable-hdpi/panel_background.9.png
+++ b/core/res/res/drawable-hdpi/panel_background.9.png
Binary files differ
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-hdpi/stat_notify_call_mute.png b/core/res/res/drawable-hdpi/stat_notify_call_mute.png
index 0cf5ef5..b0f7990 100755
--- a/core/res/res/drawable-hdpi/stat_notify_call_mute.png
+++ b/core/res/res/drawable-hdpi/stat_notify_call_mute.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png b/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png
index 0458124..76034e5 100644
--- a/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png
+++ b/core/res/res/drawable-hdpi/stat_notify_wifi_in_range.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_0.png b/core/res/res/drawable-hdpi/stat_sys_battery_0.png
index 3ed0105..82f2509 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_0.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_left.png b/core/res/res/drawable-hdpi/text_select_handle_left.png
old mode 100644
new mode 100755
index 985ae93..3743d91
--- a/core/res/res/drawable-hdpi/text_select_handle_left.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_left.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_middle.png b/core/res/res/drawable-hdpi/text_select_handle_middle.png
old mode 100644
new mode 100755
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_right.png b/core/res/res/drawable-hdpi/text_select_handle_right.png
old mode 100644
new mode 100755
index 964c5ff..12a3dff
--- a/core/res/res/drawable-hdpi/text_select_handle_right.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_right.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_media_player.9.png b/core/res/res/drawable-mdpi/btn_media_player.9.png
index 3ec3f683..c479e33 100755
--- a/core/res/res/drawable-mdpi/btn_media_player.9.png
+++ b/core/res/res/drawable-mdpi/btn_media_player.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_media_player_disabled.9.png b/core/res/res/drawable-mdpi/btn_media_player_disabled.9.png
index e74335b..8f9d8b4 100755
--- a/core/res/res/drawable-mdpi/btn_media_player_disabled.9.png
+++ b/core/res/res/drawable-mdpi/btn_media_player_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_media_player_disabled_selected.9.png b/core/res/res/drawable-mdpi/btn_media_player_disabled_selected.9.png
index 2c6517f..0ba6794 100755
--- a/core/res/res/drawable-mdpi/btn_media_player_disabled_selected.9.png
+++ b/core/res/res/drawable-mdpi/btn_media_player_disabled_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_media_player_pressed.9.png b/core/res/res/drawable-mdpi/btn_media_player_pressed.9.png
index 40bee47..f341470 100755
--- a/core/res/res/drawable-mdpi/btn_media_player_pressed.9.png
+++ b/core/res/res/drawable-mdpi/btn_media_player_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_media_player_selected.9.png b/core/res/res/drawable-mdpi/btn_media_player_selected.9.png
index 28d809f..09f4c992 100755
--- a/core/res/res/drawable-mdpi/btn_media_player_selected.9.png
+++ b/core/res/res/drawable-mdpi/btn_media_player_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_off_background.png b/core/res/res/drawable-mdpi/checkbox_off_background.png
index 6b2124f..e9471e5 100644
--- a/core/res/res/drawable-mdpi/checkbox_off_background.png
+++ b/core/res/res/drawable-mdpi/checkbox_off_background.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/checkbox_on_background.png b/core/res/res/drawable-mdpi/checkbox_on_background.png
index 56495fc..c247069 100644
--- a/core/res/res/drawable-mdpi/checkbox_on_background.png
+++ b/core/res/res/drawable-mdpi/checkbox_on_background.png
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/panel_background.9.png b/core/res/res/drawable-mdpi/panel_background.9.png
index 2305be4..822b6c6 100644
--- a/core/res/res/drawable-mdpi/panel_background.9.png
+++ b/core/res/res/drawable-mdpi/panel_background.9.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-mdpi/stat_notify_call_mute.png b/core/res/res/drawable-mdpi/stat_notify_call_mute.png
index 6da8313..4a3b057 100644
--- a/core/res/res/drawable-mdpi/stat_notify_call_mute.png
+++ b/core/res/res/drawable-mdpi/stat_notify_call_mute.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_0.png b/core/res/res/drawable-mdpi/stat_sys_battery_0.png
index 4a5e99e..a7068b2 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_0.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_0.png
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/layout/app_perms_summary.xml b/core/res/res/layout/app_perms_summary.xml
index 7160743..bdbbfcb 100755
--- a/core/res/res/layout/app_perms_summary.xml
+++ b/core/res/res/layout/app_perms_summary.xml
@@ -65,21 +65,23 @@
android:layout_marginLeft="16dip"
android:duplicateParentState="true">
- <ImageView
- android:id="@+id/show_more_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
<TextView
android:id="@+id/show_more_text"
android:textAppearance="?android:attr/textAppearanceMedium"
android:duplicateParentState="true"
- android:layout_alignTop="@id/show_more_icon"
+ android:layout_alignTop="@+id/show_more_icon"
android:layout_gravity="center_vertical"
- android:paddingLeft="6dip"
- android:layout_width="match_parent"
+ android:paddingLeft="36dip"
+ android:layout_weight="1"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content" />
+ <ImageView
+ android:id="@id/show_more_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="12dip" />
+
</LinearLayout>
<View
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 925e26c..9029ab3 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Umožňuje aplikaci uvolnit paměť telefonu smazáním souborů v adresáři mezipaměti aplikace. Přístup je velmi omezený, většinou pouze pro systémové procesy."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Přesun zdrojů aplikace"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Umožňuje aplikaci přesunout své zdroje z interní paměti na externí médium a opačně."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"čtení systémových souborů protokolu"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Umožňuje aplikaci číst různé systémové soubory protokolů. Toto nastavení aplikaci umožní získat obecné informace o činnostech s telefonem, ale neměly by obsahovat žádné osobní či soukromé informace."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"číst citlivá data v protokolech"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Umožňuje aplikaci číst různé systémové soubory protokolů. Toto nastavení aplikaci umožní získat obecné informace o činnostech s telefonem, které by mohly obsahovat osobní či soukromé informace."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"čtení nebo zápis do prostředků funkce diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Umožňuje aplikaci číst libovolné prostředky ve skupině diag, např. soubory ve složce /dev, a zapisovat do nich. Může dojít k ovlivnění stability a bezpečnosti systému. Toto nastavení by měl používat pouze výrobce či operátor pro diagnostiku hardwaru."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"povolení či zakázání komponent aplikací"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Práce"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Jiné"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Vlastní"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Vlastní"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Plocha"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Práce"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Jiné"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"pomocí <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> pomocí <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Zadejte kód PIN"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 5f0c0ee..a74b9b7 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillader, at et program frigør plads på telefonen ved at slette filer i programmets cachemappe. Adgang er normalt meget begrænset til systemprocesser."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Flyt programressourcer"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Tillader, at et program flytter programressourcer fra interne til eksterne medier og omvendt."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"læs systemlogfiler"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Tillader, at et program læser fra systemets forskellige logfiler. Dermed kan generelle oplysninger om, hvad du laver med telefonen, registreres, men logfilerne bør ikke indeholde personlige eller private oplysninger."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"læse følsomme logdata"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Tillader, at et program læser fra systemets forskellige logfiler. Dermed kan generelle oplysninger om, hvad du laver med telefonen, registreres, også personlige eller private oplysninger."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"læs/skriv til ressourcer ejet af diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Tillader, at et program læser og skriver til alle ressourcer, der ejes af diag-gruppen, som f.eks. flier i /dev. Dette kan muligvis påvirke systemets stabilitet og sikkerhed. Dette bør KUN bruges til hardwarespecifikke diagnosticeringer foretaget af producent eller udbyder."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"aktiver eller deaktiver programkomponenter"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Arbejde"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Andre"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Tilpasset"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Tilpasset"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Hjem"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Arbejde"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Andre"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Indtast PIN-kode"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 539bbe8..2f4013f 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Ermöglicht einer Anwendung, Telefonspeicher durch das Löschen von Dateien im Cache-Verzeichnis der Anwendung freizugeben. Der Zugriff beschränkt sich in der Regel auf Systemprozesse."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Anwendungsressourcen verschieben"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Ermöglicht einer Anwendung, Anwendungsressourcen von interne auf externe Medien zu verschieben und umgekehrt."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"System-Protokolldateien lesen"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Ermöglicht einer Anwendung, die verschiedenen Protokolldateien des Systems zu lesen. So können allgemeine Informationen zu den auf Ihrem Telefon durchgeführten Aktionen eingesehen werden, diese sollten jedoch keine persönlichen oder geheimen Daten enthalten."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"Lesen vertraulicher Protokolldaten"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Ermöglicht einer Anwendung, die verschiedenen Protokolldateien des Systems zu lesen. So können allgemeine Informationen zu den auf Ihrem Telefon durchgeführten Aktionen eingesehen werden. Diese können persönliche oder geheime Daten enthalten."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"Lese-/Schreibberechtigung für zu Diagnosegruppe gehörige Elemente"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Ermöglicht einer Anwendung, alle Elemente in der Diagnosegruppe zu lesen und zu bearbeiten, etwa Dateien in \"/dev\". Dies könnte eine potenzielle Gefährdung für die Stabilität und Sicherheit des Systems darstellen und sollte NUR für Hardware-spezifische Diagnosen des Herstellers oder Netzbetreibers verwendet werden."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"Anwendungskomponenten aktivieren oder deaktivieren"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Beruflich"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Andere"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Benutzerdefiniert"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Benutzerdefiniert"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Privat"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Geschäftlich"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Andere"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"über <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> über <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-Code eingeben"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ae51c55..7128ad7 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Επιτρέπει σε μια εφαρμογή να αυξήσει τον ελεύθερο χώρο αποθήκευσης του τηλεφώνου διαγράφοντας αρχεία από τον κατάλογο προσωρινής μνήμης της εφαρμογής. Η πρόσβαση είναι συνήθως πολύ περιορισμένη στη διαδικασία συστήματος."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Μετακίνηση πόρων εφαρμογής"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Επιτρέπει σε μια εφαρμογή τη μετακίνηση πόρων εφαρμογής από ένα εσωτερικό σε ένα εξωτερικό μέσο και αντίστροφα."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"ανάγνωση αρχείων καταγραφής συστήματος"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Επιτρέπει σε μια εφαρμογή να αναγνώσει τα αρχεία καταγραφής του συστήματος. Έτσι μπορεί να ανακαλύψει γενικές πληροφορίες σχετικά με τις δραστηριότητές σας στο τηλέφωνο, όμως δεν θα πρέπει να περιέχουν προσωπικές ή ιδιωτικές πληροφορίες."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"ανάγνωση ευαίσθητων δεδομένων αρχείου καταγραφής"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Επιτρέπει σε μια εφαρμογή την ανάγνωση των αρχείων καταγραφής του συστήματος. Έτσι, μπορεί να ανακαλύψει γενικές πληροφορίες σχετικά με τις δραστηριότητές σας στο τηλέφωνο, συμπεριλαμβάνοντας πιθανώς και προσωπικές ή ιδιωτικές πληροφορίες."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"ανάγνωση/εγγραφή σε πόρους που ανήκουν στο διαγνωστικό"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Επιτρέπει σε μια εφαρμογή την ανάγνωση και την εγγραφή σε πόρο που ανήκει στην ομάδα διαγνωστικού (π.χ. αρχεία στον κατάλογο /dev). Αυτό ενδέχεται να επηρεάσει την σταθερότητα και την ασφάλεια του συστήματος. Θα πρέπει να χρησιμοποιείται ΜΟΝΟ για διαγνωστικά υλικού του κατασκευαστή ή του χειριστή."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"ενεργοποίηση ή απενεργοποίηση στοιχείων εφαρμογής"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Εργασία"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Άλλο"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Προσαρμοσμένο"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Προσαρμοσμένο"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Αρχική σελίδα"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Εργασία"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Άλλο"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"μέσω <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> μέσω <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Πληκτρολογήστε τον κωδικό αριθμό PIN"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index d9d1c9b..406d335 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Admite una aplicación que libera espacio de almacenamiento en el teléfono al eliminar archivos del directorio de memoria caché de la aplicación. En general, el acceso es muy restringido para el proceso del sistema."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de la aplicación"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Permite a una aplicación mover recursos de aplicación de medios internos a externos y viceversa."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"leer archivos de registro del sistema"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Admite una aplicación que lee diversos archivos de registro del sistema. Esto le permite descubrir información general sobre lo que haces con el teléfono, pero no debe contener información personal ni privada."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"lee los datos confidenciales del registro"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Admite una aplicación que lee diversos archivos de registro del sistema. Esto te permite descubrir información general acerca de lo que haces con el teléfono, y puede potencialmente incluir información personal o privada."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"leer y escribir a recursos dentro del grupo de diagnóstico"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Admite una aplicación que lee y escribe a cualquier recurso dentro del grupo de diagnóstico; por ejemplo, archivos con /dev. Esto puede afectar potencialmente la estabilidad y la seguridad del sistema. Debe utilizarlo SÓLO el fabricante o el operador en los diagnósticos específicos del hardware."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"activar o desactivar componentes de la aplicación"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Trabajo"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Otro"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Personalizado"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Página principal"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Trabajo"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Otro"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ingresar el código de PIN"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 0cb4bab..1cf971a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite que una aplicación libere espacio de almacenamiento en el teléfono mediante la eliminación de archivos en el directorio de caché de la aplicación. El acceso al proceso del sistema suele estar muy restringido."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de aplicaciones"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que una aplicación mueva los recursos de aplicaciones de un medio interno a otro externo y viceversa."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"leer archivos de registro del sistema"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite que una aplicación lea los distintos archivos de registro del sistema. Con este permiso, la aplicación puede ver información general sobre las acciones que realiza el usuario con el teléfono, pero los registros no deberían contener información personal o privada."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"leer datos de registro personales"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite que una aplicación lea distintos archivos de registro del sistema. Con este permiso, la aplicación puede ver información general sobre las acciones que realizas con el teléfono, que puede incluir datos personales o privados."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"leer/escribir en los recursos propiedad del grupo de diagnóstico"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Permite que una aplicación lea y escriba en cualquier recurso propiedad del grupo de diagnóstico como, por ejemplo, archivos in/dev. Este permiso podría afectar a la seguridad y estabilidad del sistema. SÓLO se debe utilizar para diagnósticos específicos de hardware realizados por el fabricante o el operador."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"habilitar o inhabilitar componentes de la aplicación"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Trabajo"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Otra"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizada"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Personalizada"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Casa"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Trabajo"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Otro"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduce el código PIN"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 5f3427b..3c567f4 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permet à une application de libérer de l\'espace dans la mémoire du téléphone en supprimant des fichiers du répertoire du cache des applications. Cet accès est en général limité aux processus système."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Déplacer des ressources d\'application"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Autorise l\'application à déplacer des ressources d\'application d\'un support interne à un support externe et inversement."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"Lecture des fichiers journaux du système"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Permet à une application de lire les différents fichiers journaux du système afin d\'obtenir des informations générales sur la façon dont vous utilisez votre téléphone, sans pour autant récupérer des informations d\'ordre personnel ou privé."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"lire les données des journaux à caractère confidentiel"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Permet à une application de lire les différents fichiers journaux du système afin d\'obtenir des informations générales sur la façon dont vous utilisez votre téléphone (celles-ci peuvent éventuellement inclure des informations d\'ordre personnel ou privé)."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"Lecture/écriture dans les ressources appartenant aux diagnostics"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Permet à une application de lire et d\'éditer toute ressource appartenant au groupe de diagnostics (par exemple, les fichiers in/dev). Ceci peut affecter la stabilité et la sécurité du système. Cette fonctionnalité est UNIQUEMENT réservée aux diagnostics matériels effectués par le fabricant ou l\'opérateur."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"Activer ou désactiver des éléments de l\'application"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Bureau"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Autre"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personnalisé"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Personnalisée"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Accueil"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Professionnelle"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Autre"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Saisissez le code PIN"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 865b0e8..4e8d19a 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Consente a un\'applicazione di liberare spazio sul telefono eliminando file nella directory della cache dell\'applicazione. L\'accesso è generalmente limitato a processi di sistema."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Spostare risorse dell\'applicazione"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Consente a un\'applicazione di spostare risorse applicative da supporti interni a esterni e viceversa."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"lettura file di registro sistema"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Consente a un\'applicazione di leggere vari file di registro del sistema per trovare informazioni generali sulle operazioni effettuate con il telefono. Tali file non dovrebbero contenere informazioni personali o riservate."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"Lettura dati di registro sensibili"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Consente a un\'applicazione di leggere vari file di registro del sistema per trovare informazioni generali sulle operazioni effettuate con il telefono. Tali file potrebbero contenere informazioni personali o riservate."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"lettura/scrittura risorse di proprietà di diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Consente a un\'applicazione di leggere le risorse del gruppo diag e scrivere a esse, per esempio i file in /dev. Questa capacità potrebbe influire sulla stabilità e sicurezza del sistema. Dovrebbe essere utilizzata SOLTANTO per diagnostiche specifiche dell\'hardware effettuate dal produttore o dall\'operatore."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"attivazione/disattivazione componenti applicazioni"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Lavoro"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Altro"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizzato"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Personalizzato"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Casa"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Lavoro"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Altro"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"tramite <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> tramite <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Inserisci il PIN"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 8b0301e..a4d07a8 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"アプリケーションのキャッシュディレクトリからファイルを削除して携帯電話のメモリを解放することをアプリケーションに許可します。通常、アクセスはシステムプロセスのみに制限されます。"</string>
<string name="permlab_movePackage" msgid="728454979946503926">"アプリケーションリソースの移動"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"内部と外部のメディア間でのアプリケーションリソースの移動をアプリケーションに許可します。"</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"システムログファイルの読み取り"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"システムのさまざまなログファイルの読み取りをアプリケーションに許可します。これにより携帯電話の使用状況に関する全般情報が取得されますが、個人情報や非公開情報が含まれることはありません。"</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"機密ログデータの読み取り"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"システムの各種ログファイルの読み取りをアプリケーションに許可します。許可すると端末の使用状況に関する全般的な情報が読み取られます。この情報には個人情報や機密情報が含まれる場合があります。"</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"diagが所有するリソースの読み書き"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"diagグループが所有するリソース(例:/dev内のファイル)への読み書きをアプリケーションに許可します。システムの安定性とセキュリティに影響する恐れがあります。メーカー/オペレーターによるハードウェア固有の診断以外には使用しないでください。"</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"アプリケーションのコンポーネントを有効/無効にする"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"勤務先"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"その他"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"カスタム"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"カスタム"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"自宅"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"勤務先"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"その他"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>経由"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>、更新元: <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PINコードを入力"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 75d0da1..56495d6 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"애플리케이션이 애플리케이션 캐시 디렉토리에 있는 파일을 삭제하여 휴대전화의 저장공간을 늘릴 수 있도록 합니다. 액세스는 일반적으로 시스템 프로세스로 제한됩니다."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"애플리케이션 리소스 이동"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"애플리케이션이 애플리케이션 리소스를 내부에서 외부 미디어로 또는 그 반대로 이동할 수 있도록 합니다."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"시스템 로그 파일 읽기"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"애플리케이션이 시스템의 다양한 로그 파일을 읽을 수 있도록 합니다. 이 경우 애플리케이션은 사용자가 휴대전화로 수행하는 작업에 대한 일반적인 정보를 검색할 수 있습니다. 하지만 로그 파일에 어떠한 개인정보도 포함되어서는 안 됩니다."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"중요한 로그 데이터 읽기"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"애플리케이션이 시스템의 다양한 로그 파일을 읽을 수 있도록 합니다. 이렇게 되면 애플리케이션은 개인정보 또는 비공개 정보를 포함하여 휴대전화로 수행하는 작업에 대한 일반적인 정보를 검색할 수 있습니다."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"진단 그룹 소유의 리소스 읽기/쓰기"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"애플리케이션이 진단 그룹 소유의 리소스(예: /dev에 있는 파일)를 읽고 쓸 수 있도록 합니다. 이 기능은 시스템 안정성 및 보안에 영향을 미칠 수 있으므로 제조업체 또는 사업자가 하드웨어 관련 진단을 수행하는 경우에만 사용해야 합니다."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"애플리케이션 구성 요소 사용 또는 사용 안함"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"직장"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"기타"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"맞춤설정"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"맞춤설정"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"홈"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"직장"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"기타"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>을(를) 통해"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>(<xliff:g id="SOURCE">%2$s</xliff:g> 사용)"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN 코드 입력"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 70ee877..375e592 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Lar applikasjonen frigjøre lagringsplass ved å slette filer i applikasjoners hurtigbufferkatalog. Tilgangen er vanligvis sterkt begrenset, til systemprosesser."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Flytter programressurser"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Gir et program tillatelse til å flytte programressurser fra interne til eksterne medier og omvendt."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"lese systemets loggfiler"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Lar applikasjonen lese fra diverse loggfiler på systemet. Disse inneholder generell informasjon om hva som gjøres med telefonen, men skal ikke inneholde personlig eller privat informasjon."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"les sensitive loggdata"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Lar programmet lese fra diverse loggfiler på systemet. Disse inneholder generell informasjon om hva som gjøres med telefonen, og kan inneholde personlig eller privat informasjon."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"lese/skrive ressurser eid av diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Lar applikasjonen lese og skrive enhver ressurs eid av gruppen diag; for eksempel, filer i /dev. Dette kan potensielt påvirke systemets sikkerhet og stabilitet. Dette bør KUN brukes for maskinvarespesifikke diagnoseverktøy laget av operatøren eller produsenten."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"aktivere eller deaktigere applikasjonskomponenter"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Arbeid"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Annen"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Egendefinert"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Egendefinert"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Startside"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Arbeid"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Annen"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Skriv inn PIN-kode:"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 2d07166..2dedd7b 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Hiermee kan een toepassing opslagruimte op de telefoon vrij maken door bestanden te verwijderen uit de cachemap van de toepassing. De toegang is doorgaans beperkt tot het systeemproces."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Toepassingsbronnen verplaatsen"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Een toepassing toestaan toepassingsbronnen te verplaatsen van interne naar externe media en omgekeerd."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"systeemlogbestanden lezen"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Hiermee kan een toepassing de verschillende logbestanden van het systeem lezen. De toepassing kan op deze manier algemene informatie achterhalen over uw telefoongebruik. Hierin is geen persoonlijke of privé-informatie opgenomen."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"gevoelige logbestandsgegevens lezen"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Hiermee kan een toepassing de verschillende logbestanden van het systeem lezen. De toepassing kan op deze manier algemene informatie achterhalen over uw telefoongebruik, mogelijk inclusief persoonlijke of privé-informatie."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"lezen/schrijven naar bronnen van diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Hiermee kan een toepassing lezen en schrijven naar elke bron die hoort bij de diagnostische groep, zoals bestanden in /dev. Hierdoor kan de systeemstabiliteit en -veiligheid worden beïnvloed. Dit mag ALLEEN worden gebruikt voor hardwarespecifieke diagnostiek door de fabrikant of operator."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"toepassingscomponenten in- of uitschakelen"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Werk"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Overig"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Aangepast"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Aangepast"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Startpagina"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Werk"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Overig"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-code invoeren"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 7604c61..4e5256f 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Pozwala aplikacji na zwalnianie pamięci telefonu przez usuwanie plików z katalogu pamięci podręcznej aplikacji. Dostęp jest bardzo ograniczony, z reguły do procesów systemu."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Przenoszenie zasobów aplikacji"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Zezwala aplikacji na przeniesienie zasobów aplikacji z nośnika wewnętrznego na zewnętrzny i odwrotnie."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"czytanie plików dziennika systemu"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Umożliwia aplikacji czytanie różnych plików dziennika systemowego. Pozwala to na uzyskanie ogólnych informacji o czynnościach wykonywanych w telefonie, ale bez ujawniania danych osobowych lub osobistych informacji."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"odczyt wrażliwych danych dziennika"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Umożliwia aplikacjom odczyt z różnych plików dzienników systemowych. Pozwala to poznać ogólne informacje na temat korzystania z telefonu, co potencjalnie może obejmować również informacje prywatne lub osobiste."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"czytanie/zapisywanie w zasobach należących do diagnostyki"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Pozwala aplikacji na czytanie i zapisywanie we wszystkich zasobach posiadanych przez diagnozowaną grupę, jak na przykład pliki w katalogu /dev. Może to potencjalnie wpłynąć na stabilność i bezpieczeństwo systemu. Powinno być wykorzystywane TYLKO w celach diagnozowania sprzętu przez producenta lub operatora."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"włączanie lub wyłączanie składników aplikacji"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Służbowy"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Inny"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Niestandardowy"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Niestandardowy"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Domowy"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Służbowy"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Inny"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"przez <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> przez <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Wprowadź kod PIN"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 892aefd..55fdd18 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite a uma aplicação libertar espaço de armazenamento no telefone eliminando ficheiros no directório da cache da aplicação. Geralmente, o acesso é muito limitado para processamento do sistema."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de aplicações"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que uma aplicação mova recursos de aplicações de meios internos para meios externos e vice-versa."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"ler ficheiros de registo do sistema"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite a uma aplicação ler a partir dos diversos ficheiros de registo do sistema. Isto permite descobrir informações gerais sobre a forma como o utilizador usa o telefone, mas estas não devem conter quaisquer dados pessoais ou privados."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"ler dados sensíveis de registo"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite a uma aplicação ler a partir dos diversos ficheiros de registo do sistema. Isto permite descobrir informações gerais sobre a forma como o utilizador usa o telefone, podendo, inclusive, incluir dados pessoais ou privados."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"ler/escrever em recursos propriedade de diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Permite a uma aplicação ler e escrever em qualquer recurso que seja propriedade do grupo diag. Por exemplo, ficheiros em /dev. Isto pode afectar potencialmente a estabilidade e a segurança do sistema e deve ser utilizado APENAS para diagnósticos específicos do hardware pelo fabricante ou pelo operador."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"activar ou desactivar componentes da aplicação"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Emprego"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Outro"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Personalizado"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Página inicial"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Emprego"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Outro"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"através do <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> através de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduzir código PIN"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 4c18677..d5e1ca0 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite que um aplicativo libere o espaço de armazenamento do telefone excluindo arquivos no diretório de cache do aplicativo. O acesso é normalmente muito restrito para o processo do sistema."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos do aplicativo"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que um aplicativo mova os recursos do aplicativo da mídia interna para a externa e vice-versa."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"ler arquivos de registro do sistema"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite que um aplicativo leia os diversos arquivos de registro do sistema. Isso permite que ele descubra informações gerais sobre o que você está fazendo com o telefone, porém esses arquivos não devem conter informações pessoais ou privadas."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"ler dados de registro de informações confidenciais"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Permite que um aplicativo leia os diversos arquivos de registro do sistema. Isso permite que ele descubra informações gerais sobre o que você está fazendo com o telefone, inclusive possíveis informações pessoais ou privadas."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"ler/gravar em recursos pertencentes ao diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Permite que um aplicativo leia e grave em qualquer recurso que pertença ao grupo de diagnósticos; por exemplo, arquivos em /dev. Isso possivelmente pode afetar a estabilidade e a segurança do sistema. Isso deve ser usado APENAS para diagnósticos específicos do hardware realizados pelo fabricante ou pelo operador."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"ativar ou desativar os componentes do aplicativo"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Comercial"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Outros"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Personalizado"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Página inicial"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Comercial"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Outros"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"por meio de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Digite o código PIN"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index a366a85..b61635f 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Позволяет приложению освобождать память телефона с помощью удаления файлов из каталога кэша приложений. Обычно это разрешается только системным процессам."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Перемещать ресурсы приложения"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Позволяет приложению перемещать ресурсы приложения с внутренних на внешние носители и наоборот."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"считывать системные файлы журналов"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Позволяет приложению считывать информацию из различных журналов системы. Приложение может получить сведения о работе пользователя с телефоном, но они не должны содержать какой-либо личной или конфиденциальной информации."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"считывать конфиденциальные данные журнала"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Позволяет приложению считывать информацию из различных журналов системы. Приложение может получать сведения о работе пользователя с телефоном, которые могут содержать личную или конфиденциальную информацию."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"считывать/записывать данные в ресурсы, принадлежащие группе диагностики"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Позволяет приложению считывать и записывать данные в любые ресурсы, принадлежащие группе диагностики (например, файлы в каталоге /dev). Это может повлиять на стабильность и безопасность системы. Эта возможность может быть использована ТОЛЬКО производителем или оператором для диагностики аппаратного обеспечения."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"включать или отключать компоненты приложения"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Работа"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Другое"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Создать свой ярлык"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Особый"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Домашний"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Рабочий"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Другой"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"с помощью <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> с помощью <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Введите PIN-код"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 137f853..8bc598d 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillåter att ett program frigör lagringsutrymme i telefonen genom att ta bort filer i programmets katalog för cachelagring. Åtkomst är mycket begränsad, vanligtvis till systemprocesser."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Flytta programresurser"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Tillåter att ett program flyttar programresurser från interna till externa medier och tvärt om."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"läsa systemets loggfiler"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Tillåter att ett program läser från systemets olika loggfiler. Det innebär att programmet kan upptäcka allmän information om vad du gör med telefonen, men den bör inte innehålla personlig eller privat information."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"läsa känsliga loggdata"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Tillåter att ett program läser från systemets olika loggfiler. Det innebär att programmet kan upptäcka allmän information om vad du gör med telefonen, vilket kan inkludera personlig eller privat information."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"läsa/skriva till resurser som ägs av diag"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Tillåter att ett program läser och skriver till en resurs som ägs av diag-gruppen; till exempel filer i /dev. Detta kan eventuellt påverka systemets stabilitet och säkerhet. Detta bör ENDAST används av tillverkaren eller operatören för maskinvaruspecifik diagnostik."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"aktivera eller inaktivera programkomponenter"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"Arbete"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Övrigt"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Anpassad"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Anpassad"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Startsida"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"Arbete"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Övrigt"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ange PIN-kod"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index ad3b5e2..58eb42a 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Uygulamaların uygulama önbelleği dizinindeki dosyaları silerek telefonda yer açmasına izin verir. Erişim genellikle sistem işlemlerine ve yüksek düzeyde kısıtlı olarak verilir."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Uygulama kaynaklarını taşı"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Bir uygulamanın, uygulama kaynaklarını dahili ve harici ortamlar arasında taşımasına olanak tanır."</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"sistem günlük dosyalarını oku"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"Uygulamaların sistemin çeşitli günlük dosyalarından okumalarına izin verir. Bu, uygulamaların telefon ile neler yaptığınız ile ilgili genel bilgi bulmasına izin verir, ancak bunlar kişisel veya özel bir bilgi içermemelidir."</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"hassas günlük verilerini okuma"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"Bir uygulamanın, sistemin çeşitli günlük dosyalarındaki bilgileri okumasına izin verir. Bu izin, uygulamanın, telefonda yaptıklarınızla ilgili genel bilgileri bulmasına olanak sağlar ve bunlar kişisel ve gizli bilgilerinizi de içerebilir."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"sahibi tanılama olan kaynakları oku/bunlara yaz"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"Uygulamanın tanılama grubundaki bir kaynağa ait herhangi bir kaynağı; örneğin /dev içindeki dosyaları okumasına ve bunlara yazmasına izin verir. Bu işlevin sistem kararlılığını ve güvenliğini olumsuz etkileme olasılığı vardır. Üretici veya operatör tarafından YALNIZCA donanıma özgü tanılama için kullanılmalıdır."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"uygulama bileşenlerini etkinleştir veya devre dışı bırak"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"İş"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Diğer"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Özel"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Özel"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"Ev"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"İş"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"Diğer"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g> aracılığıyla"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="SOURCE">%2$s</xliff:g> ile <xliff:g id="DATE">%1$s</xliff:g> tarihinde"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN kodunu gir"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 4c7d72a..aad56fd 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"允许应用程序通过删除应用程序缓存目录中的文件释放手机存储空间。通常此权限只适用于系统进程。"</string>
<string name="permlab_movePackage" msgid="728454979946503926">"移动应用程序资源"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"允许应用程序在内部介质和外部介质之间移动应用程序资源。"</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"读取系统日志文件"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"允许应用程序从系统的各日志文件中读取信息。这样应用程序可以发现您的手机使用情况,但这些信息不应包含任何个人信息或保密信息。"</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"查阅敏感日志数据"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"允许应用程序从系统的各个日志文件中读取信息。这样,应用程序就可以发现关于您手机使用情况的一般信息,其中可能包含个人信息或私密信息。"</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"读取/写入诊断所拥有的资源"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"允许应用程序读取/写入诊断组所拥有的任何资源(例如,/dev 中的文件)。这可能会影响系统稳定性和安全性。此权限仅供制造商或运营商诊断硬件问题。"</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"启用或停用应用程序组件"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"公司"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"其他"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"自定义"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"自定义"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"住宅"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"单位"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"其他"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"通过 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"时间:<xliff:g id="DATE">%1$s</xliff:g>,方式:<xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"输入 PIN 码"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 2439042..6490c55 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -267,8 +267,8 @@
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"允許應用程式刪除快取目錄裡的檔案,釋放儲存空間。此操作通常受到系統程序嚴格限制。"</string>
<string name="permlab_movePackage" msgid="728454979946503926">"移動應用程式資源"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"允許應用程式將應用程式資源從內部媒體移到外部媒體,反之亦可。"</string>
- <!-- outdated translation 4811921703882532070 --> <string name="permlab_readLogs" msgid="6615778543198967614">"讀取系統記錄檔"</string>
- <!-- outdated translation 2257937955580475902 --> <string name="permdesc_readLogs" msgid="8896449437464867766">"允許應用程式讀取系統記錄檔。此項操作可讓應用程式了解目前手機操作狀態,但內容應不含任何個人或隱私資訊。"</string>
+ <string name="permlab_readLogs" msgid="6615778543198967614">"讀取機密記錄資料"</string>
+ <string name="permdesc_readLogs" msgid="8896449437464867766">"允許應用程式讀取系統的各種記錄檔。這會允許應用程式搜尋一般性的電話使用資訊,可能包含您的個人或私人資訊。"</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"讀寫 diag 擁有的資源"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"允許應用程式讀寫 diag 群組的資源;例如:/dev 裡的檔案。這可能會影響系統穩定性與安全性。此功能僅供製造商或技術人員用於硬體規格偵測。"</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"啟用或停用應用程式元件"</string>
@@ -544,14 +544,10 @@
<string name="orgTypeWork" msgid="29268870505363872">"公司"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"其他"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"自訂"</string>
- <!-- no translation found for sipAddressTypeCustom (2473580593111590945) -->
- <skip />
- <!-- no translation found for sipAddressTypeHome (6093598181069359295) -->
- <skip />
- <!-- no translation found for sipAddressTypeWork (6920725730797099047) -->
- <skip />
- <!-- no translation found for sipAddressTypeOther (4408436162950119849) -->
- <skip />
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"自訂"</string>
+ <string name="sipAddressTypeHome" msgid="6093598181069359295">"住家"</string>
+ <string name="sipAddressTypeWork" msgid="6920725730797099047">"公司"</string>
+ <string name="sipAddressTypeOther" msgid="4408436162950119849">"其他"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"透過 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>透過「<xliff:g id="SOURCE">%2$s</xliff:g>」"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"輸入 PIN 碼"</string>
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/res/res/values/themes.xml b/core/res/res/values/themes.xml
index e7cb593..040e732 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -137,8 +137,8 @@
<item name="scrollbarTrackVertical">@null</item>
<!-- Text selection handle attributes -->
- <item name="textSelectHandleLeft">@android:drawable/text_select_handle_middle</item>
- <item name="textSelectHandleRight">@android:drawable/text_select_handle_middle</item>
+ <item name="textSelectHandleLeft">@android:drawable/text_select_handle_left</item>
+ <item name="textSelectHandleRight">@android:drawable/text_select_handle_right</item>
<item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
<item name="textSelectHandleWindowStyle">@android:style/Widget.TextSelectHandle</item>
diff --git a/core/tests/coretests/res/raw/test1.obb b/core/tests/coretests/res/raw/test1.obb
new file mode 100644
index 0000000..170e36f
--- /dev/null
+++ b/core/tests/coretests/res/raw/test1.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_nosig.obb b/core/tests/coretests/res/raw/test1_nosig.obb
new file mode 100644
index 0000000..5c3573f7
--- /dev/null
+++ b/core/tests/coretests/res/raw/test1_nosig.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_wrongpackage.obb b/core/tests/coretests/res/raw/test1_wrongpackage.obb
new file mode 100644
index 0000000..2e02eaa
--- /dev/null
+++ b/core/tests/coretests/res/raw/test1_wrongpackage.obb
Binary files differ
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
index e9311e0..2d0424d 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
@@ -133,14 +133,12 @@
private class HeadsetServiceListener implements ServiceListener {
private boolean mConnected = false;
- @Override
public void onServiceConnected() {
synchronized (this) {
mConnected = true;
}
}
- @Override
public void onServiceDisconnected() {
synchronized (this) {
mConnected = false;
@@ -321,6 +319,9 @@
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mReceiver, filter);
+
+ mA2dp = new BluetoothA2dp(mContext);
+ mHeadset = new BluetoothHeadset(mContext, mHeadsetServiceListener);
}
public void close() {
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/core/tests/coretests/src/com/android/server/MountServiceTests.java b/core/tests/coretests/src/com/android/server/MountServiceTests.java
new file mode 100644
index 0000000..83e9d18
--- /dev/null
+++ b/core/tests/coretests/src/com/android/server/MountServiceTests.java
@@ -0,0 +1,210 @@
+/*
+ * 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.server;
+
+import com.android.frameworks.coretests.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.storage.OnObbStateChangeListener;
+import android.os.storage.StorageManager;
+import android.test.AndroidTestCase;
+import android.test.ComparisonFailure;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+
+public class MountServiceTests extends AndroidTestCase {
+ private static final String TAG = "MountServiceTests";
+
+ private static final long MAX_WAIT_TIME = 25*1000;
+ private static final long WAIT_TIME_INCR = 5*1000;
+
+ private static final String OBB_MOUNT_PREFIX = "/mnt/obb/";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ private static void assertStartsWith(String message, String prefix, String actual) {
+ if (!actual.startsWith(prefix)) {
+ throw new ComparisonFailure(message, prefix, actual);
+ }
+ }
+
+ private interface CompletableTask {
+ public boolean isDone();
+ }
+
+ private static class ObbObserver extends OnObbStateChangeListener implements CompletableTask {
+ public String path;
+ public String state;
+ boolean done = false;
+
+ @Override
+ public void onObbStateChange(String path, String state) {
+ synchronized (this) {
+ this.path = path;
+ this.state = state;
+ done = true;
+ notifyAll();
+ }
+ }
+
+ public void reset() {
+ this.path = null;
+ this.state = null;
+ done = false;
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+ }
+
+ private boolean waitForCompletion(CompletableTask task) {
+ long waitTime = 0;
+ synchronized (task) {
+ while (!task.isDone() && waitTime < MAX_WAIT_TIME) {
+ try {
+ task.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Interrupted during sleep", e);
+ }
+ }
+ }
+
+ return task.isDone();
+ }
+ private File getFilePath(String name) {
+ final File filesDir = mContext.getFilesDir();
+ final File outFile = new File(filesDir, name);
+ return outFile;
+ }
+
+ private void copyRawToFile(int rawResId, File outFile) {
+ Resources res = mContext.getResources();
+ InputStream is = null;
+ try {
+ is = res.openRawResource(rawResId);
+ } catch (NotFoundException e) {
+ fail("Failed to load resource with id: " + rawResId);
+ }
+ FileUtils.setPermissions(outFile.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+ | FileUtils.S_IRWXO, -1, -1);
+ assertTrue(FileUtils.copyToFile(is, outFile));
+ FileUtils.setPermissions(outFile.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+ | FileUtils.S_IRWXO, -1, -1);
+ }
+
+ private StorageManager getStorageManager() {
+ return (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
+ }
+
+ private void mountObb(StorageManager sm, final int resource, final File file,
+ String expectedState) {
+ copyRawToFile(resource, file);
+
+ ObbObserver observer = new ObbObserver();
+ assertTrue("mountObb call on " + file.getPath() + " should succeed",
+ sm.mountObb(file.getPath(), null, observer));
+
+ assertTrue("Mount should have completed",
+ waitForCompletion(observer));
+
+ assertEquals("Actual file and resolved file should be the same",
+ file.getPath(), observer.path);
+
+ assertEquals(expectedState, observer.state);
+ }
+
+ private String checkMountedPath(StorageManager sm, File file) {
+ final String mountPath = sm.getMountedObbPath(file.getPath());
+ assertStartsWith("Path should be in " + OBB_MOUNT_PREFIX,
+ OBB_MOUNT_PREFIX,
+ mountPath);
+ return mountPath;
+ }
+
+ private void unmountObb(StorageManager sm, final File outFile) {
+ ObbObserver observer = new ObbObserver();
+ assertTrue("unmountObb call on test1.obb should succeed",
+ sm.unmountObb(outFile.getPath(), false, observer));
+
+ assertTrue("Unmount should have completed",
+ waitForCompletion(observer));
+ }
+
+ @LargeTest
+ public void testMountAndUnmountObbNormal() {
+ StorageManager sm = getStorageManager();
+
+ final File outFile = getFilePath("test1.obb");
+
+ mountObb(sm, R.raw.test1, outFile, Environment.MEDIA_MOUNTED);
+
+ final String mountPath = checkMountedPath(sm, outFile);
+ final File mountDir = new File(mountPath);
+
+ assertTrue("OBB mounted path should be a directory",
+ mountDir.isDirectory());
+
+ unmountObb(sm, outFile);
+ }
+
+ @LargeTest
+ public void testAttemptMountNonObb() {
+ StorageManager sm = getStorageManager();
+
+ final File outFile = getFilePath("test1_nosig.obb");
+
+ mountObb(sm, R.raw.test1_nosig, outFile, Environment.MEDIA_BAD_REMOVAL);
+
+ assertFalse("OBB should not be mounted",
+ sm.isObbMounted(outFile.getPath()));
+
+ assertNull("OBB's mounted path should be null",
+ sm.getMountedObbPath(outFile.getPath()));
+ }
+
+ @LargeTest
+ public void testAttemptMountObbWrongPackage() {
+ StorageManager sm = getStorageManager();
+
+ final File outFile = getFilePath("test1_wrongpackage.obb");
+
+ mountObb(sm, R.raw.test1_wrongpackage, outFile, Environment.MEDIA_BAD_REMOVAL);
+
+ assertFalse("OBB should not be mounted",
+ sm.isObbMounted(outFile.getPath()));
+
+ assertNull("OBB's mounted path should be null",
+ sm.getMountedObbPath(outFile.getPath()));
+ }
+}
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—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 @@
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver></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="android.permission.BIND_DEVICE_ADMIN"
+
+<li><code>android:permission="android.permission.BIND_DEVICE_ADMIN"
</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 {
-...
+...
@Override
public void onEnabled(Context context, Intent intent) {
showToast(context, "Sample Device Admin: enabled");
@@ -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,
"Additional text explaining why this needs to be added.");
@@ -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>
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
+ "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
+</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 <head>}, 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>
+<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+</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
+<head>}. Instead, provide your CSS and JavaScript directly in the <head> (or
+at the end of the <body>, 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 <meta>} tag (which must
+be placed in your document {@code <head>}). You can define multiple viewport properties in the
+{@code <meta>} 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>
+<head>
+ <title>Example</title>
+ <meta name="viewport" content="width=device-width, user-scalable=no" />
+</head>
+</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>
+<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]
+ " />
+</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>
+<meta name="viewport" content="width=320" />
+</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>
+<meta name="viewport" content="width=device-width" />
+</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–10.</p>
+
+<p>For example:</p>
+
+<pre>
+<meta name="viewport" content="initial-scale=1.0" />
+</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><value></code></em> - Specify a dpi value to use as the target dpi. Values must
+be within the range 70–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>
+<meta name="viewport" content="target-densitydpi=device-dpi, width=device-width" />
+</pre>
+
+<p>Figure 4 shows a web page using these viewport settings—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—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>
+<link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 1.5)" href="hdpi.css" />
+<link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 1.0)" href="mdpi.css" />
+<link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 0.75)" href="ldpi.css" />
+</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);
+}
+
+@media screen and (-webkit-device-pixel-ratio: 1.5) {
+ // CSS for high-density screens
+ #header {
+ background:url(high-density-image.png);
+ }
+}
+
+@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>
+<meta name="viewport" content="target-densitydpi=device-dpi, width=device-width" />
+</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—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—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
+<WebView>} element in your activity layout. For example, here's a layout file in which the
+{@link android.webkit.WebView} fills the screen:</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<WebView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/webview"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+/>
+</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>
+<manifest ... >
+ <uses-permission android:name="android.permission.INTERNET" />
+ ...
+</manifest>
+</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>
+<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />
+
+<script type="text/javascript">
+ function showAndroidToast(toast) {
+ Android.showToast(toast);
+ }
+</script>
+</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—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 {
+ @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>
+@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) && 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/ui/Input.h b/include/ui/Input.h
index ee40b85..66061fd 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -71,6 +71,8 @@
/*
* Flags that flow alongside events in the input dispatch system to help with certain
* policy decisions such as waking from device sleep.
+ *
+ * These flags are also defined in frameworks/base/core/java/android/view/WindowManagerPolicy.java.
*/
enum {
/* These flags originate in RawEvents and are generally set in the key map.
@@ -102,6 +104,11 @@
// Indicates that the screen was dim when the event was received and the event
// should brighten the device.
POLICY_FLAG_BRIGHT_HERE = 0x20000000,
+
+ // Indicates that the event should be dispatched to applications.
+ // The input event should still be sent to the InputDispatcher so that it can see all
+ // input events received include those that it will not deliver.
+ POLICY_FLAG_PASS_TO_USER = 0x40000000,
};
/*
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index 47c5326..0834e86 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -282,10 +282,35 @@
*/
virtual int32_t getMaxEventsPerSecond() = 0;
+ /* Intercepts a key event immediately before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptKeyBeforeQueueing(nsecs_t when, int32_t deviceId,
+ int32_t action, int32_t& flags, int32_t keyCode, int32_t scanCode,
+ uint32_t& policyFlags) = 0;
+
+ /* Intercepts a generic touch, trackball or other event before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0;
+
/* Allows the policy a chance to intercept a key before dispatching. */
virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+ /* Notifies the policy about switch events.
+ */
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;
+
/* Poke user activity for an event dispatched to a window. */
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
@@ -333,6 +358,8 @@
int32_t metaState, int32_t edgeFlags,
uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
float xPrecision, float yPrecision, nsecs_t downTime) = 0;
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;
/* Injects an input event and optionally waits for sync.
* The synchronization mode determines whether the method blocks while waiting for
@@ -408,6 +435,8 @@
int32_t metaState, int32_t edgeFlags,
uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
float xPrecision, float yPrecision, nsecs_t downTime);
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ;
virtual int32_t injectInputEvent(const InputEvent* event,
int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
@@ -447,6 +476,7 @@
mutable int32_t refCount;
int32_t type;
nsecs_t eventTime;
+ uint32_t policyFlags;
InjectionState* injectionState;
bool dispatchInProgress; // initially false, set to true while dispatching
@@ -460,7 +490,6 @@
struct KeyEntry : EventEntry {
int32_t deviceId;
int32_t source;
- uint32_t policyFlags;
int32_t action;
int32_t flags;
int32_t keyCode;
@@ -489,7 +518,6 @@
struct MotionEntry : EventEntry {
int32_t deviceId;
int32_t source;
- uint32_t policyFlags;
int32_t action;
int32_t flags;
int32_t metaState;
@@ -664,7 +692,8 @@
Pool<DispatchEntry> mDispatchEntryPool;
Pool<CommandEntry> mCommandEntryPool;
- void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime);
+ void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime,
+ uint32_t policyFlags);
void releaseEventEntryInjectionState(EventEntry* entry);
};
@@ -685,21 +714,19 @@
BROKEN
};
+ // Specifies the sources to cancel.
+ enum CancelationOptions {
+ CANCEL_ALL_EVENTS = 0,
+ CANCEL_POINTER_EVENTS = 1,
+ CANCEL_NON_POINTER_EVENTS = 2,
+ };
+
InputState();
~InputState();
// Returns true if there is no state to be canceled.
bool isNeutral() const;
- // Returns true if the input state believes it is out of sync.
- bool isOutOfSync() const;
-
- // Sets the input state to be out of sync if it is not neutral.
- void setOutOfSync();
-
- // Resets the input state out of sync flag.
- void resetOutOfSync();
-
// Records tracking information for an event that has just been published.
// Returns whether the event is consistent with the current input state.
Consistency trackEvent(const EventEntry* entry);
@@ -712,16 +739,14 @@
// Returns whether the event is consistent with the current input state.
Consistency trackMotion(const MotionEntry* entry);
- // Synthesizes cancelation events for the current state.
- void synthesizeCancelationEvents(Allocator* allocator,
- Vector<EventEntry*>& outEvents) const;
+ // Synthesizes cancelation events for the current state and resets the tracked state.
+ void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
+ Vector<EventEntry*>& outEvents, CancelationOptions options);
// Clears the current state.
void clear();
private:
- bool mIsOutOfSync;
-
struct KeyMemento {
int32_t deviceId;
int32_t source;
@@ -745,6 +770,8 @@
Vector<KeyMemento> mKeyMementos;
Vector<MotionMemento> mMotionMementos;
+
+ static bool shouldCancelEvent(int32_t eventSource, CancelationOptions options);
};
/* Manages the dispatch state associated with a single input channel. */
@@ -794,6 +821,13 @@
status_t initialize();
};
+ enum DropReason {
+ DROP_REASON_NOT_DROPPED = 0,
+ DROP_REASON_POLICY = 1,
+ DROP_REASON_APP_SWITCH = 2,
+ DROP_REASON_DISABLED = 3,
+ };
+
sp<InputDispatcherPolicyInterface> mPolicy;
Mutex mLock;
@@ -813,12 +847,16 @@
// Enqueues an inbound event. Returns true if mLooper->wake() should be called.
bool enqueueInboundEventLocked(EventEntry* entry);
+ // Cleans up input state when dropping an inbound event.
+ void dropInboundEventLocked(EventEntry* entry, DropReason dropReason);
+
// App switch latency optimization.
+ bool mAppSwitchSawKeyDown;
nsecs_t mAppSwitchDueTime;
- static bool isAppSwitchKey(int32_t keyCode);
+ static bool isAppSwitchKeyCode(int32_t keyCode);
+ bool isAppSwitchKeyEventLocked(KeyEntry* keyEntry);
bool isAppSwitchPendingLocked();
- bool detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry);
void resetPendingAppSwitchLocked(bool handled);
// All registered connections mapped by receive pipe file descriptor.
@@ -840,7 +878,7 @@
// Event injection and synchronization.
Condition mInjectionResultAvailableCondition;
- EventEntry* createEntryFromInjectedInputEventLocked(const InputEvent* event);
+ bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
Condition mInjectionSyncFinishedCondition;
@@ -875,7 +913,7 @@
void drainInboundQueueLocked();
void releasePendingEventLocked();
void releaseInboundEventLocked(EventEntry* entry);
- bool isEventFromReliableSourceLocked(EventEntry* entry);
+ bool isEventFromTrustedSourceLocked(EventEntry* entry);
// Dispatch state.
bool mDispatchEnabled;
@@ -984,11 +1022,17 @@
void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
- void abortDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
- bool broken);
+ void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
void drainOutboundQueueLocked(Connection* connection);
static int handleReceiveCallback(int receiveFd, int events, void* data);
+ void synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CancelationOptions options, const char* reason);
+ void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel,
+ InputState::CancelationOptions options, const char* reason);
+ void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
+ InputState::CancelationOptions options, const char* reason);
+
// Splitting motion events across windows.
MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds);
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index 3619189..c15e382 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -87,49 +87,12 @@
ROTATION_270 = 3
};
- /* Actions returned by interceptXXX methods. */
- enum {
- // The input dispatcher should do nothing and discard the input unless other
- // flags are set.
- ACTION_NONE = 0,
-
- // The input dispatcher should dispatch the input to the application.
- ACTION_DISPATCH = 0x00000001,
- };
-
/* Gets information about the display with the specified id.
* Returns true if the display info is available, false otherwise.
*/
virtual bool getDisplayInfo(int32_t displayId,
int32_t* width, int32_t* height, int32_t* orientation) = 0;
- /* Intercepts a key event.
- * The policy can use this method as an opportunity to perform power management functions
- * and early event preprocessing such as updating policy flags.
- *
- * Returns a policy action constant such as ACTION_DISPATCH.
- */
- virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
- bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) = 0;
-
- /* Intercepts a switch event.
- * The policy can use this method as an opportunity to perform power management functions
- * and early event preprocessing such as updating policy flags.
- *
- * Switches are not dispatched to applications so this method should
- * usually return ACTION_NONE.
- */
- virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
- uint32_t& policyFlags) = 0;
-
- /* Intercepts a generic touch, trackball or other event.
- * The policy can use this method as an opportunity to perform power management functions
- * and early event preprocessing such as updating policy flags.
- *
- * Returns a policy action constant such as ACTION_DISPATCH.
- */
- virtual int32_t interceptGeneric(nsecs_t when, uint32_t& policyFlags) = 0;
-
/* Determines whether to turn on some hacks we have to improve the touch interaction with a
* certain device whose screen currently is not all that good.
*/
@@ -403,8 +366,6 @@
protected:
InputDevice* mDevice;
InputReaderContext* mContext;
-
- bool applyStandardPolicyActions(nsecs_t when, int32_t policyActions);
};
@@ -466,8 +427,6 @@
void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
uint32_t policyFlags);
- void applyPolicyAndDispatch(nsecs_t when, uint32_t policyFlags,
- bool down, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime);
ssize_t findKeyDownLocked(int32_t scanCode);
};
@@ -525,8 +484,6 @@
void initializeLocked();
void sync(nsecs_t when);
- void applyPolicyAndDispatch(nsecs_t when, int32_t motionEventAction,
- PointerCoords* pointerCoords, nsecs_t downTime);
};
@@ -829,10 +786,6 @@
BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
int32_t motionEventAction);
- void applyPolicyAndDispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
- int32_t keyEventAction, int32_t keyEventFlags,
- int32_t keyCode, int32_t scanCode, nsecs_t downTime);
-
bool isPointInsideSurfaceLocked(int32_t x, int32_t y);
const VirtualKey* findVirtualKeyHitLocked(int32_t x, int32_t y);
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/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index fe8555d..58c2cdf 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -95,16 +95,19 @@
return true;
}
-static bool isValidMotionAction(int32_t action) {
+static bool isValidMotionAction(int32_t action, size_t pointerCount) {
switch (action & AMOTION_EVENT_ACTION_MASK) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL:
case AMOTION_EVENT_ACTION_MOVE:
- case AMOTION_EVENT_ACTION_POINTER_DOWN:
- case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_OUTSIDE:
return true;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ int32_t index = getMotionEventActionPointerIndex(action);
+ return index >= 0 && size_t(index) < pointerCount;
+ }
default:
return false;
}
@@ -112,7 +115,7 @@
static bool validateMotionEvent(int32_t action, size_t pointerCount,
const int32_t* pointerIds) {
- if (! isValidMotionAction(action)) {
+ if (! isValidMotionAction(action, pointerCount)) {
LOGE("Motion event has invalid action code 0x%x", action);
return false;
}
@@ -235,16 +238,6 @@
resetKeyRepeatLocked();
}
- // If dispatching is disabled, drop all events in the queue.
- if (! mDispatchEnabled) {
- if (mPendingEvent || ! mInboundQueue.isEmpty()) {
- LOGI("Dropping pending events because input dispatch is disabled.");
- releasePendingEventLocked();
- drainInboundQueueLocked();
- }
- return;
- }
-
// If dispatching is frozen, do not process timeouts or try to deliver any new events.
if (mDispatchFrozen) {
#if DEBUG_FOCUS
@@ -294,7 +287,11 @@
// samples may be appended to this event by the time the throttling timeout
// expires.
// TODO Make this smarter and consider throttling per device independently.
- if (entry->type == EventEntry::TYPE_MOTION) {
+ if (entry->type == EventEntry::TYPE_MOTION
+ && !isAppSwitchDue
+ && mDispatchEnabled
+ && (entry->policyFlags & POLICY_FLAG_PASS_TO_USER)
+ && !entry->isInjected()) {
MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
int32_t deviceId = motionEntry->deviceId;
uint32_t source = motionEntry->source;
@@ -347,39 +344,43 @@
// Now we have an event to dispatch.
assert(mPendingEvent != NULL);
bool done = false;
+ DropReason dropReason = DROP_REASON_NOT_DROPPED;
+ if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+ dropReason = DROP_REASON_POLICY;
+ } else if (!mDispatchEnabled) {
+ dropReason = DROP_REASON_DISABLED;
+ }
switch (mPendingEvent->type) {
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
ConfigurationChangedEntry* typedEntry =
static_cast<ConfigurationChangedEntry*>(mPendingEvent);
done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
break;
}
case EventEntry::TYPE_KEY: {
KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
- bool appSwitchKey = isAppSwitchKey(typedEntry->keyCode);
- bool dropEvent = isAppSwitchDue && ! appSwitchKey;
- done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout, dropEvent,
- nextWakeupTime);
- if (done) {
- if (dropEvent) {
- LOGI("Dropped key because of pending overdue app switch.");
- } else if (appSwitchKey) {
+ if (isAppSwitchDue) {
+ if (isAppSwitchKeyEventLocked(typedEntry)) {
resetPendingAppSwitchLocked(true);
+ isAppSwitchDue = false;
+ } else if (dropReason == DROP_REASON_NOT_DROPPED) {
+ dropReason = DROP_REASON_APP_SWITCH;
}
}
+ done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout,
+ dropReason != DROP_REASON_NOT_DROPPED, nextWakeupTime);
break;
}
case EventEntry::TYPE_MOTION: {
MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
- bool dropEvent = isAppSwitchDue;
- done = dispatchMotionLocked(currentTime, typedEntry, dropEvent, nextWakeupTime);
- if (done) {
- if (dropEvent) {
- LOGI("Dropped motion because of pending overdue app switch.");
- }
+ if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DROP_REASON_APP_SWITCH;
}
+ done = dispatchMotionLocked(currentTime, typedEntry,
+ dropReason != DROP_REASON_NOT_DROPPED, nextWakeupTime);
break;
}
@@ -389,6 +390,10 @@
}
if (done) {
+ if (dropReason != DROP_REASON_NOT_DROPPED) {
+ dropInboundEventLocked(mPendingEvent, dropReason);
+ }
+
releasePendingEventLocked();
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
@@ -399,36 +404,83 @@
mInboundQueue.enqueueAtTail(entry);
switch (entry->type) {
- case EventEntry::TYPE_KEY:
- needWake |= detectPendingAppSwitchLocked(static_cast<KeyEntry*>(entry));
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
+ if (isAppSwitchKeyEventLocked(keyEntry)) {
+ if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
+ mAppSwitchSawKeyDown = true;
+ } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ if (mAppSwitchSawKeyDown) {
+#if DEBUG_APP_SWITCH
+ LOGD("App switch is pending!");
+#endif
+ mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
+ mAppSwitchSawKeyDown = false;
+ needWake = true;
+ }
+ }
+ }
break;
}
+ }
return needWake;
}
-bool InputDispatcher::isAppSwitchKey(int32_t keyCode) {
+void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) {
+ const char* reason;
+ switch (dropReason) {
+ case DROP_REASON_POLICY:
+ reason = "inbound event was dropped because the policy requested that it not be "
+ "delivered to the application";
+ break;
+ case DROP_REASON_DISABLED:
+ LOGI("Dropped event because input dispatch is disabled.");
+ reason = "inbound event was dropped because input dispatch is disabled";
+ break;
+ case DROP_REASON_APP_SWITCH:
+ LOGI("Dropped event because of pending overdue app switch.");
+ reason = "inbound event was dropped because of pending overdue app switch";
+ break;
+ default:
+ assert(false);
+ return;
+ }
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY:
+ synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CANCEL_NON_POINTER_EVENTS, reason);
+ break;
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+ synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CANCEL_POINTER_EVENTS, reason);
+ } else {
+ synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CANCEL_NON_POINTER_EVENTS, reason);
+ }
+ break;
+ }
+ }
+}
+
+bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
}
+bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
+ return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+ && isAppSwitchKeyCode(keyEntry->keyCode)
+ && isEventFromTrustedSourceLocked(keyEntry)
+ && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER);
+}
+
bool InputDispatcher::isAppSwitchPendingLocked() {
return mAppSwitchDueTime != LONG_LONG_MAX;
}
-bool InputDispatcher::detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry) {
- if (inboundKeyEntry->action == AKEY_EVENT_ACTION_UP
- && ! (inboundKeyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
- && isAppSwitchKey(inboundKeyEntry->keyCode)
- && isEventFromReliableSourceLocked(inboundKeyEntry)) {
-#if DEBUG_APP_SWITCH
- LOGD("App switch is pending!");
-#endif
- mAppSwitchDueTime = inboundKeyEntry->eventTime + APP_SWITCH_TIMEOUT;
- return true; // need wake
- }
- return false;
-}
-
void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
mAppSwitchDueTime = LONG_LONG_MAX;
@@ -489,12 +541,10 @@
mAllocator.releaseEventEntry(entry);
}
-bool InputDispatcher::isEventFromReliableSourceLocked(EventEntry* entry) {
+bool InputDispatcher::isEventFromTrustedSourceLocked(EventEntry* entry) {
InjectionState* injectionState = entry->injectionState;
return ! injectionState
- || injectionState->injectorUid == 0
- || mPolicy->checkInjectEventsPermissionNonReentrant(
- injectionState->injectorPid, injectionState->injectorUid);
+ || hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid);
}
void InputDispatcher::resetKeyRepeatLocked() {
@@ -509,7 +559,7 @@
KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
// Reuse the repeated key entry if it is otherwise unreferenced.
- uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
+ uint32_t policyFlags = entry->policyFlags & (POLICY_FLAG_RAW_MASK | POLICY_FLAG_PASS_TO_USER);
if (entry->refCount == 1) {
mAllocator.recycleKeyEntry(entry);
entry->eventTime = currentTime;
@@ -565,7 +615,7 @@
if (! dropEvent && mFocusedWindow) {
trusted = checkInjectionPermission(mFocusedWindow, entry->injectionState);
} else {
- trusted = isEventFromReliableSourceLocked(entry);
+ trusted = isEventFromTrustedSourceLocked(entry);
}
if (trusted) {
CommandEntry* commandEntry = postCommandLocked(
@@ -793,9 +843,11 @@
prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
resumeWithAppendedMotionSample);
} else {
- LOGW("Framework requested delivery of an input event to channel '%s' but it "
- "is not registered with the input dispatcher.",
+#if DEBUG_FOCUS
+ LOGD("Dropping event delivery to target with channel '%s' because it "
+ "is no longer registered with the input dispatcher.",
inputTarget.inputChannel->getName().string());
+#endif
}
}
}
@@ -876,7 +928,9 @@
ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
- connection->inputState.setOutOfSync();
+ synthesizeCancelationEventsForConnectionLocked(
+ connection, InputState::CANCEL_ALL_EVENTS,
+ "application not responding");
}
}
}
@@ -1236,7 +1290,9 @@
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
// First pointer went down.
if (mTouchState.down) {
- LOGW("Pointer down received while already down.");
+#if DEBUG_FOCUS
+ LOGD("Pointer down received while already down.");
+#endif
}
} else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
// One pointer went up.
@@ -1307,23 +1363,19 @@
bool InputDispatcher::checkInjectionPermission(const InputWindow* window,
const InjectionState* injectionState) {
if (injectionState
- && injectionState->injectorUid > 0
- && (window == NULL || window->ownerUid != injectionState->injectorUid)) {
- bool result = mPolicy->checkInjectEventsPermissionNonReentrant(
- injectionState->injectorPid, injectionState->injectorUid);
- if (! result) {
- if (window) {
- LOGW("Permission denied: injecting event from pid %d uid %d to window "
- "with input channel %s owned by uid %d",
- injectionState->injectorPid, injectionState->injectorUid,
- window->inputChannel->getName().string(),
- window->ownerUid);
- } else {
- LOGW("Permission denied: injecting event from pid %d uid %d",
- injectionState->injectorPid, injectionState->injectorUid);
- }
- return false;
+ && (window == NULL || window->ownerUid != injectionState->injectorUid)
+ && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
+ if (window) {
+ LOGW("Permission denied: injecting event from pid %d uid %d to window "
+ "with input channel %s owned by uid %d",
+ injectionState->injectorPid, injectionState->injectorUid,
+ window->inputChannel->getName().string(),
+ window->ownerUid);
+ } else {
+ LOGW("Permission denied: injecting event from pid %d uid %d",
+ injectionState->injectorPid, injectionState->injectorUid);
}
+ return false;
}
return true;
}
@@ -1408,8 +1460,10 @@
// Skip this event if the connection status is not normal.
// We don't want to enqueue additional outbound events if the connection is broken.
if (connection->status != Connection::STATUS_NORMAL) {
- LOGW("channel '%s' ~ Dropping event because the channel status is %s",
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ Dropping event because the channel status is %s",
connection->getInputChannelName(), connection->getStatusLabel());
+#endif
return;
}
@@ -1508,40 +1562,6 @@
}
}
- // Bring the input state back in line with reality in case it drifted off during an ANR.
- if (connection->inputState.isOutOfSync()) {
- mTempCancelationEvents.clear();
- connection->inputState.synthesizeCancelationEvents(& mAllocator, mTempCancelationEvents);
- connection->inputState.resetOutOfSync();
-
- if (! mTempCancelationEvents.isEmpty()) {
- LOGI("channel '%s' ~ Generated %d cancelation events to bring channel back in sync "
- "with reality.",
- connection->getInputChannelName(), mTempCancelationEvents.size());
-
- for (size_t i = 0; i < mTempCancelationEvents.size(); i++) {
- EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i);
- switch (cancelationEventEntry->type) {
- case EventEntry::TYPE_KEY:
- logOutboundKeyDetailsLocked(" ",
- static_cast<KeyEntry*>(cancelationEventEntry));
- break;
- case EventEntry::TYPE_MOTION:
- logOutboundMotionDetailsLocked(" ",
- static_cast<MotionEntry*>(cancelationEventEntry));
- break;
- }
-
- DispatchEntry* cancelationDispatchEntry =
- mAllocator.obtainDispatchEntry(cancelationEventEntry,
- 0, inputTarget->xOffset, inputTarget->yOffset); // increments ref
- connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
-
- mAllocator.releaseEventEntry(cancelationEventEntry);
- }
- }
- }
-
// This is a new event.
// Enqueue a new dispatch entry onto the outbound queue for this connection.
DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
@@ -1635,7 +1655,7 @@
if (status) {
LOGE("channel '%s' ~ Could not publish key event, "
"status=%d", connection->getInputChannelName(), status);
- abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
return;
}
break;
@@ -1685,7 +1705,7 @@
if (status) {
LOGE("channel '%s' ~ Could not publish motion event, "
"status=%d", connection->getInputChannelName(), status);
- abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
return;
}
@@ -1706,7 +1726,7 @@
LOGE("channel '%s' ~ Could not append motion sample "
"for a reason other than out of memory, status=%d",
connection->getInputChannelName(), status);
- abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
return;
}
}
@@ -1727,7 +1747,7 @@
if (status) {
LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
connection->getInputChannelName(), status);
- abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
return;
}
@@ -1764,7 +1784,7 @@
if (status) {
LOGE("channel '%s' ~ Could not reset publisher, status=%d",
connection->getInputChannelName(), status);
- abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
return;
}
@@ -1806,28 +1826,23 @@
deactivateConnectionLocked(connection.get());
}
-void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection, bool broken) {
+void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
#if DEBUG_DISPATCH_CYCLE
- LOGD("channel '%s' ~ abortDispatchCycle - broken=%s",
+ LOGD("channel '%s' ~ abortBrokenDispatchCycle - broken=%s",
connection->getInputChannelName(), toString(broken));
#endif
- // Input state will no longer be realistic.
- connection->inputState.setOutOfSync();
-
// Clear the outbound queue.
drainOutboundQueueLocked(connection.get());
- // Handle the case where the connection appears to be unrecoverably broken.
+ // The connection appears to be unrecoverably broken.
// Ignore already broken or zombie connections.
- if (broken) {
- if (connection->status == Connection::STATUS_NORMAL) {
- connection->status = Connection::STATUS_BROKEN;
+ if (connection->status == Connection::STATUS_NORMAL) {
+ connection->status = Connection::STATUS_BROKEN;
- // Notify other system components.
- onDispatchCycleBrokenLocked(currentTime, connection);
- }
+ // Notify other system components.
+ onDispatchCycleBrokenLocked(currentTime, connection);
}
}
@@ -1862,7 +1877,7 @@
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. "
"events=0x%x", connection->getInputChannelName(), events);
- d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ d->abortBrokenDispatchCycleLocked(currentTime, connection);
d->runCommandsLockedInterruptible();
return 0; // remove the callback
}
@@ -1877,7 +1892,7 @@
if (status) {
LOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
connection->getInputChannelName(), status);
- d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ d->abortBrokenDispatchCycleLocked(currentTime, connection);
d->runCommandsLockedInterruptible();
return 0; // remove the callback
}
@@ -1888,6 +1903,77 @@
} // release lock
}
+void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CancelationOptions options, const char* reason) {
+ for (size_t i = 0; i < mConnectionsByReceiveFd.size(); i++) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByReceiveFd.valueAt(i), options, reason);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
+ const sp<InputChannel>& channel, InputState::CancelationOptions options,
+ const char* reason) {
+ ssize_t index = getConnectionIndexLocked(channel);
+ if (index >= 0) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByReceiveFd.valueAt(index), options, reason);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
+ const sp<Connection>& connection, InputState::CancelationOptions options,
+ const char* reason) {
+ nsecs_t currentTime = now();
+
+ mTempCancelationEvents.clear();
+ connection->inputState.synthesizeCancelationEvents(currentTime, & mAllocator,
+ mTempCancelationEvents, options);
+
+ if (! mTempCancelationEvents.isEmpty()
+ && connection->status != Connection::STATUS_BROKEN) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync "
+ "with reality: %s, options=%d.",
+ connection->getInputChannelName(), mTempCancelationEvents.size(), reason, options);
+#endif
+ for (size_t i = 0; i < mTempCancelationEvents.size(); i++) {
+ EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i);
+ switch (cancelationEventEntry->type) {
+ case EventEntry::TYPE_KEY:
+ logOutboundKeyDetailsLocked("cancel - ",
+ static_cast<KeyEntry*>(cancelationEventEntry));
+ break;
+ case EventEntry::TYPE_MOTION:
+ logOutboundMotionDetailsLocked("cancel - ",
+ static_cast<MotionEntry*>(cancelationEventEntry));
+ break;
+ }
+
+ int32_t xOffset, yOffset;
+ const InputWindow* window = getWindowLocked(connection->inputChannel);
+ if (window) {
+ xOffset = -window->frameLeft;
+ yOffset = -window->frameTop;
+ } else {
+ xOffset = 0;
+ yOffset = 0;
+ }
+
+ DispatchEntry* cancelationDispatchEntry =
+ mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref
+ 0, xOffset, yOffset);
+ connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+
+ mAllocator.releaseEventEntry(cancelationEventEntry);
+ }
+
+ if (!connection->outboundQueue.headSentinel.next->inProgress) {
+ startDispatchCycleLocked(currentTime, connection);
+ }
+ }
+}
+
InputDispatcher::MotionEntry*
InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) {
assert(pointerIds.value != 0);
@@ -1999,6 +2085,9 @@
return;
}
+ mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags,
+ keyCode, scanCode, /*byref*/ policyFlags);
+
bool needWake;
{ // acquire lock
AutoMutex _l(mLock);
@@ -2041,6 +2130,8 @@
return;
}
+ mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);
+
bool needWake;
{ // acquire lock
AutoMutex _l(mLock);
@@ -2165,6 +2256,16 @@
}
}
+void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
+ uint32_t policyFlags) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ LOGD("notifySwitch - switchCode=%d, switchValue=%d, policyFlags=0x%x",
+ switchCode, switchValue, policyFlags);
+#endif
+
+ mPolicy->notifySwitch(when, switchCode, switchValue, policyFlags);
+}
+
int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
#if DEBUG_INBOUND_EVENT_DETAILS
@@ -2174,27 +2275,84 @@
#endif
nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
+ bool trusted = hasInjectionPermission(injectorPid, injectorUid);
- InjectionState* injectionState;
- bool needWake;
- { // acquire lock
- AutoMutex _l(mLock);
-
- EventEntry* injectedEntry = createEntryFromInjectedInputEventLocked(event);
- if (! injectedEntry) {
+ EventEntry* injectedEntry;
+ switch (event->getType()) {
+ case AINPUT_EVENT_TYPE_KEY: {
+ const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+ int32_t action = keyEvent->getAction();
+ if (! validateKeyEvent(action)) {
return INPUT_EVENT_INJECTION_FAILED;
}
- injectionState = mAllocator.obtainInjectionState(injectorPid, injectorUid);
- if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
- injectionState->injectionIsAsync = true;
+ nsecs_t eventTime = keyEvent->getEventTime();
+ int32_t deviceId = keyEvent->getDeviceId();
+ int32_t flags = keyEvent->getFlags();
+ int32_t keyCode = keyEvent->getKeyCode();
+ int32_t scanCode = keyEvent->getScanCode();
+ uint32_t policyFlags = POLICY_FLAG_INJECTED;
+ if (trusted) {
+ mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags,
+ keyCode, scanCode, /*byref*/ policyFlags);
}
- injectionState->refCount += 1;
- injectedEntry->injectionState = injectionState;
+ mLock.lock();
+ injectedEntry = mAllocator.obtainKeyEntry(eventTime, deviceId, keyEvent->getSource(),
+ policyFlags, action, flags, keyCode, scanCode, keyEvent->getMetaState(),
+ keyEvent->getRepeatCount(), keyEvent->getDownTime());
+ break;
+ }
- needWake = enqueueInboundEventLocked(injectedEntry);
- } // release lock
+ case AINPUT_EVENT_TYPE_MOTION: {
+ const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+ int32_t action = motionEvent->getAction();
+ size_t pointerCount = motionEvent->getPointerCount();
+ const int32_t* pointerIds = motionEvent->getPointerIds();
+ if (! validateMotionEvent(action, pointerCount, pointerIds)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ nsecs_t eventTime = motionEvent->getEventTime();
+ uint32_t policyFlags = POLICY_FLAG_INJECTED;
+ if (trusted) {
+ mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);
+ }
+
+ mLock.lock();
+ const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
+ const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
+ MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), uint32_t(pointerCount),
+ pointerIds, samplePointerCoords);
+ for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
+ sampleEventTimes += 1;
+ samplePointerCoords += pointerCount;
+ mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords);
+ }
+ injectedEntry = motionEntry;
+ break;
+ }
+
+ default:
+ LOGW("Cannot inject event of type %d", event->getType());
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ InjectionState* injectionState = mAllocator.obtainInjectionState(injectorPid, injectorUid);
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionState->injectionIsAsync = true;
+ }
+
+ injectionState->refCount += 1;
+ injectedEntry->injectionState = injectionState;
+
+ bool needWake = enqueueInboundEventLocked(injectedEntry);
+ mLock.unlock();
if (needWake) {
mLooper->wake();
@@ -2260,6 +2418,11 @@
return injectionResult;
}
+bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) {
+ return injectorUid == 0
+ || mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+}
+
void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
InjectionState* injectionState = entry->injectionState;
if (injectionState) {
@@ -2310,59 +2473,6 @@
}
}
-InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventLocked(
- const InputEvent* event) {
- switch (event->getType()) {
- case AINPUT_EVENT_TYPE_KEY: {
- const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
- if (! validateKeyEvent(keyEvent->getAction())) {
- return NULL;
- }
-
- uint32_t policyFlags = POLICY_FLAG_INJECTED;
-
- KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
- keyEvent->getDeviceId(), keyEvent->getSource(), policyFlags,
- keyEvent->getAction(), keyEvent->getFlags(),
- keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
- keyEvent->getRepeatCount(), keyEvent->getDownTime());
- return keyEntry;
- }
-
- case AINPUT_EVENT_TYPE_MOTION: {
- const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
- if (! validateMotionEvent(motionEvent->getAction(),
- motionEvent->getPointerCount(), motionEvent->getPointerIds())) {
- return NULL;
- }
-
- uint32_t policyFlags = POLICY_FLAG_INJECTED;
-
- const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
- const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
- size_t pointerCount = motionEvent->getPointerCount();
-
- MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
- motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
- motionEvent->getAction(), motionEvent->getFlags(),
- motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
- motionEvent->getXPrecision(), motionEvent->getYPrecision(),
- motionEvent->getDownTime(), uint32_t(pointerCount),
- motionEvent->getPointerIds(), samplePointerCoords);
- for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
- sampleEventTimes += 1;
- samplePointerCoords += pointerCount;
- mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords);
- }
- return motionEntry;
- }
-
- default:
- assert(false);
- return NULL;
- }
-}
-
const InputWindow* InputDispatcher::getWindowLocked(const sp<InputChannel>& inputChannel) {
for (size_t i = 0; i < mWindows.size(); i++) {
const InputWindow* window = & mWindows[i];
@@ -2381,7 +2491,12 @@
AutoMutex _l(mLock);
// Clear old window pointers.
- mFocusedWindow = NULL;
+ sp<InputChannel> oldFocusedWindowChannel;
+ if (mFocusedWindow) {
+ oldFocusedWindowChannel = mFocusedWindow->inputChannel;
+ mFocusedWindow = NULL;
+ }
+
mWindows.clear();
// Loop over new windows and rebuild the necessary window pointers for
@@ -2397,6 +2512,24 @@
}
}
+ if (oldFocusedWindowChannel != NULL) {
+ if (!mFocusedWindow || oldFocusedWindowChannel != mFocusedWindow->inputChannel) {
+#if DEBUG_FOCUS
+ LOGD("Focus left window: %s",
+ oldFocusedWindowChannel->getName().string());
+#endif
+ synthesizeCancelationEventsForInputChannelLocked(oldFocusedWindowChannel,
+ InputState::CANCEL_NON_POINTER_EVENTS, "focus left window");
+ oldFocusedWindowChannel.clear();
+ }
+ }
+ if (mFocusedWindow && oldFocusedWindowChannel == NULL) {
+#if DEBUG_FOCUS
+ LOGD("Focus entered window: %s",
+ mFocusedWindow->inputChannel->getName().string());
+#endif
+ }
+
for (size_t i = 0; i < mTouchState.windows.size(); ) {
TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i);
const InputWindow* window = getWindowLocked(touchedWindow.channel);
@@ -2404,12 +2537,17 @@
touchedWindow.window = window;
i += 1;
} else {
+#if DEBUG_FOCUS
+ LOGD("Touched window was removed: %s", touchedWindow.channel->getName().string());
+#endif
mTouchState.windows.removeAt(i);
+ synthesizeCancelationEventsForInputChannelLocked(touchedWindow.channel,
+ InputState::CANCEL_POINTER_EVENTS, "touched window was removed");
}
}
#if DEBUG_FOCUS
- logDispatchStateLocked();
+ //logDispatchStateLocked();
#endif
} // release lock
@@ -2432,7 +2570,7 @@
}
#if DEBUG_FOCUS
- logDispatchStateLocked();
+ //logDispatchStateLocked();
#endif
} // release lock
@@ -2469,7 +2607,7 @@
}
#if DEBUG_FOCUS
- logDispatchStateLocked();
+ //logDispatchStateLocked();
#endif
} // release lock
@@ -2571,11 +2709,10 @@
for (size_t i = 0; i < mActiveConnections.size(); i++) {
const Connection* connection = mActiveConnections[i];
dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u"
- "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n",
+ "inputState.isNeutral=%s\n",
i, connection->getInputChannelName(), connection->getStatusLabel(),
connection->outboundQueue.count(),
- toString(connection->inputState.isNeutral()),
- toString(connection->inputState.isOutOfSync()));
+ toString(connection->inputState.isNeutral()));
}
} else {
dump.append(INDENT "ActiveConnections: <none>\n");
@@ -2656,7 +2793,7 @@
mLooper->removeFd(inputChannel->getReceivePipeFd());
nsecs_t currentTime = now();
- abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
runCommandsLockedInterruptible();
} // release lock
@@ -2837,11 +2974,12 @@
}
void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type,
- nsecs_t eventTime) {
+ nsecs_t eventTime, uint32_t policyFlags) {
entry->type = type;
entry->refCount = 1;
entry->dispatchInProgress = false;
entry->eventTime = eventTime;
+ entry->policyFlags = policyFlags;
entry->injectionState = NULL;
}
@@ -2855,7 +2993,7 @@
InputDispatcher::ConfigurationChangedEntry*
InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) {
ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
- initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime);
+ initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime, 0);
return entry;
}
@@ -2864,11 +3002,10 @@
int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
int32_t repeatCount, nsecs_t downTime) {
KeyEntry* entry = mKeyEntryPool.alloc();
- initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime);
+ initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime, policyFlags);
entry->deviceId = deviceId;
entry->source = source;
- entry->policyFlags = policyFlags;
entry->action = action;
entry->flags = flags;
entry->keyCode = keyCode;
@@ -2887,12 +3024,11 @@
nsecs_t downTime, uint32_t pointerCount,
const int32_t* pointerIds, const PointerCoords* pointerCoords) {
MotionEntry* entry = mMotionEntryPool.alloc();
- initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime);
+ initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime, policyFlags);
entry->eventTime = eventTime;
entry->deviceId = deviceId;
entry->source = source;
- entry->policyFlags = policyFlags;
entry->action = action;
entry->flags = flags;
entry->metaState = metaState;
@@ -3039,8 +3175,7 @@
// --- InputDispatcher::InputState ---
-InputDispatcher::InputState::InputState() :
- mIsOutOfSync(false) {
+InputDispatcher::InputState::InputState() {
}
InputDispatcher::InputState::~InputState() {
@@ -3050,20 +3185,6 @@
return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
}
-bool InputDispatcher::InputState::isOutOfSync() const {
- return mIsOutOfSync;
-}
-
-void InputDispatcher::InputState::setOutOfSync() {
- if (! isNeutral()) {
- mIsOutOfSync = true;
- }
-}
-
-void InputDispatcher::InputState::resetOutOfSync() {
- mIsOutOfSync = false;
-}
-
InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent(
const EventEntry* entry) {
switch (entry->type) {
@@ -3090,9 +3211,6 @@
switch (action) {
case AKEY_EVENT_ACTION_UP:
mKeyMementos.removeAt(i);
- if (isNeutral()) {
- mIsOutOfSync = false;
- }
return CONSISTENT;
case AKEY_EVENT_ACTION_DOWN:
@@ -3132,9 +3250,6 @@
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL:
mMotionMementos.removeAt(i);
- if (isNeutral()) {
- mIsOutOfSync = false;
- }
return CONSISTENT;
case AMOTION_EVENT_ACTION_DOWN:
@@ -3192,30 +3307,52 @@
}
}
-void InputDispatcher::InputState::synthesizeCancelationEvents(
- Allocator* allocator, Vector<EventEntry*>& outEvents) const {
- for (size_t i = 0; i < mKeyMementos.size(); i++) {
+void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
+ Allocator* allocator, Vector<EventEntry*>& outEvents,
+ CancelationOptions options) {
+ for (size_t i = 0; i < mKeyMementos.size(); ) {
const KeyMemento& memento = mKeyMementos.itemAt(i);
- outEvents.push(allocator->obtainKeyEntry(now(),
- memento.deviceId, memento.source, 0,
- AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_CANCELED,
- memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
+ if (shouldCancelEvent(memento.source, options)) {
+ outEvents.push(allocator->obtainKeyEntry(currentTime,
+ memento.deviceId, memento.source, 0,
+ AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_CANCELED,
+ memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
+ mKeyMementos.removeAt(i);
+ } else {
+ i += 1;
+ }
}
for (size_t i = 0; i < mMotionMementos.size(); i++) {
const MotionMemento& memento = mMotionMementos.itemAt(i);
- outEvents.push(allocator->obtainMotionEntry(now(),
- memento.deviceId, memento.source, 0,
- AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
- memento.xPrecision, memento.yPrecision, memento.downTime,
- memento.pointerCount, memento.pointerIds, memento.pointerCoords));
+ if (shouldCancelEvent(memento.source, options)) {
+ outEvents.push(allocator->obtainMotionEntry(currentTime,
+ memento.deviceId, memento.source, 0,
+ AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
+ memento.xPrecision, memento.yPrecision, memento.downTime,
+ memento.pointerCount, memento.pointerIds, memento.pointerCoords));
+ mMotionMementos.removeAt(i);
+ } else {
+ i += 1;
+ }
}
}
void InputDispatcher::InputState::clear() {
mKeyMementos.clear();
mMotionMementos.clear();
- mIsOutOfSync = false;
+}
+
+bool InputDispatcher::InputState::shouldCancelEvent(int32_t eventSource,
+ CancelationOptions options) {
+ switch (options) {
+ case CANCEL_POINTER_EVENTS:
+ return eventSource & AINPUT_SOURCE_CLASS_POINTER;
+ case CANCEL_NON_POINTER_EVENTS:
+ return !(eventSource & AINPUT_SOURCE_CLASS_POINTER);
+ default:
+ return true;
+ }
}
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 8e173aa..0560bb8 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -796,10 +796,6 @@
return 0;
}
-bool InputMapper::applyStandardPolicyActions(nsecs_t when, int32_t policyActions) {
- return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH;
-}
-
// --- SwitchInputMapper ---
@@ -823,11 +819,7 @@
}
void SwitchInputMapper::processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) {
- uint32_t policyFlags = 0;
- int32_t policyActions = getPolicy()->interceptSwitch(
- when, switchCode, switchValue, policyFlags);
-
- applyStandardPolicyActions(when, policyActions);
+ getDispatcher()->notifySwitch(when, switchCode, switchValue, 0);
}
int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
@@ -983,29 +975,9 @@
getContext()->updateGlobalMetaState();
}
- applyPolicyAndDispatch(when, policyFlags, down, keyCode, scanCode, newMetaState, downTime);
-}
-
-void KeyboardInputMapper::applyPolicyAndDispatch(nsecs_t when, uint32_t policyFlags, bool down,
- int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
- int32_t policyActions = getPolicy()->interceptKey(when,
- getDeviceId(), down, keyCode, scanCode, policyFlags);
-
- if (! applyStandardPolicyActions(when, policyActions)) {
- return; // event dropped
- }
-
- int32_t keyEventAction = down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP;
- int32_t keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM;
- if (policyFlags & POLICY_FLAG_WOKE_HERE) {
- keyEventFlags |= AKEY_EVENT_FLAG_WOKE_HERE;
- }
- if (policyFlags & POLICY_FLAG_VIRTUAL) {
- keyEventFlags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
- }
-
getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
- keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+ down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
}
ssize_t KeyboardInputMapper::findKeyDownLocked(int32_t scanCode) {
@@ -1215,26 +1187,13 @@
}
} // release lock
- applyPolicyAndDispatch(when, motionEventAction, & pointerCoords, downTime);
-
- mAccumulator.clear();
-}
-
-void TrackballInputMapper::applyPolicyAndDispatch(nsecs_t when, int32_t motionEventAction,
- PointerCoords* pointerCoords, nsecs_t downTime) {
- uint32_t policyFlags = 0;
- int32_t policyActions = getPolicy()->interceptGeneric(when, policyFlags);
-
- if (! applyStandardPolicyActions(when, policyActions)) {
- return; // event dropped
- }
-
int32_t metaState = mContext->getGlobalMetaState();
int32_t pointerId = 0;
-
- getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TRACKBALL, policyFlags,
+ getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TRACKBALL, 0,
motionEventAction, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
- 1, & pointerId, pointerCoords, mXPrecision, mYPrecision, downTime);
+ 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime);
+
+ mAccumulator.clear();
}
int32_t TrackballInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
@@ -2012,15 +1971,7 @@
}
void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
- // Apply generic policy actions.
-
uint32_t policyFlags = 0;
- int32_t policyActions = getPolicy()->interceptGeneric(when, policyFlags);
-
- if (! applyStandardPolicyActions(when, policyActions)) {
- mLastTouch.clear();
- return; // event dropped
- }
// Preprocess pointer data.
@@ -2160,24 +2111,11 @@
} // release lock
// Dispatch virtual key.
- applyPolicyAndDispatchVirtualKey(when, policyFlags, keyEventAction, keyEventFlags,
- keyCode, scanCode, downTime);
- return touchResult;
-}
-
-void TouchInputMapper::applyPolicyAndDispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
- int32_t keyEventAction, int32_t keyEventFlags,
- int32_t keyCode, int32_t scanCode, nsecs_t downTime) {
int32_t metaState = mContext->getGlobalMetaState();
-
policyFlags |= POLICY_FLAG_VIRTUAL;
- int32_t policyActions = getPolicy()->interceptKey(when, getDeviceId(),
- keyEventAction == AKEY_EVENT_ACTION_DOWN, keyCode, scanCode, policyFlags);
-
- if (applyStandardPolicyActions(when, policyActions)) {
- getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
- keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
- }
+ getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
+ keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+ return touchResult;
}
void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
@@ -2447,7 +2385,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..0f4c1f3 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -28,6 +28,7 @@
#include "ASessionDescription.h"
#include <ctype.h>
+#include <cutils/properties.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -35,6 +36,9 @@
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaErrors.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
// If no access units are received within 3 secs, assume that the rtp
// stream has ended and signal end of stream.
static int64_t kAccessUnitTimeoutUs = 3000000ll;
@@ -45,6 +49,19 @@
namespace android {
+static void MakeUserAgentString(AString *s) {
+ s->setTo("stagefright/1.1 (Linux;Android ");
+
+#if (PROPERTY_VALUE_MAX < 8)
+#error "PROPERTY_VALUE_MAX must be at least 8"
+#endif
+
+ char value[PROPERTY_VALUE_MAX];
+ property_get("ro.build.version.release", value, "Unknown");
+ s->append(value);
+ s->append(")");
+}
+
static bool GetAttribute(const char *s, const char *key, AString *value) {
value->clear();
@@ -86,8 +103,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 +133,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();
}
@@ -134,6 +154,131 @@
return maxTimeUs;
}
+ static void addRR(const sp<ABuffer> &buf) {
+ uint8_t *ptr = buf->data() + buf->size();
+ ptr[0] = 0x80 | 0;
+ ptr[1] = 201; // RR
+ ptr[2] = 0;
+ ptr[3] = 1;
+ ptr[4] = 0xde; // SSRC
+ ptr[5] = 0xad;
+ ptr[6] = 0xbe;
+ ptr[7] = 0xef;
+
+ buf->setRange(0, buf->size() + 8);
+ }
+
+ static void addSDES(int s, const sp<ABuffer> &buffer) {
+ struct sockaddr_in addr;
+ socklen_t addrSize = sizeof(addr);
+ CHECK_EQ(0, getsockname(s, (sockaddr *)&addr, &addrSize));
+
+ uint8_t *data = buffer->data() + buffer->size();
+ data[0] = 0x80 | 1;
+ data[1] = 202; // SDES
+ data[4] = 0xde; // SSRC
+ data[5] = 0xad;
+ data[6] = 0xbe;
+ data[7] = 0xef;
+
+ size_t offset = 8;
+
+ data[offset++] = 1; // CNAME
+
+ AString cname = "stagefright@";
+ cname.append(inet_ntoa(addr.sin_addr));
+ data[offset++] = cname.size();
+
+ memcpy(&data[offset], cname.c_str(), cname.size());
+ offset += cname.size();
+
+ data[offset++] = 6; // TOOL
+
+ AString tool;
+ MakeUserAgentString(&tool);
+
+ data[offset++] = tool.size();
+
+ memcpy(&data[offset], tool.c_str(), tool.size());
+ offset += tool.size();
+
+ data[offset++] = 0;
+
+ if ((offset % 4) > 0) {
+ size_t count = 4 - (offset % 4);
+ switch (count) {
+ case 3:
+ data[offset++] = 0;
+ case 2:
+ data[offset++] = 0;
+ case 1:
+ data[offset++] = 0;
+ }
+ }
+
+ size_t numWords = (offset / 4) - 1;
+ data[2] = numWords >> 8;
+ data[3] = numWords & 0xff;
+
+ buffer->setRange(buffer->offset(), buffer->size() + offset);
+ }
+
+ // In case we're behind NAT, fire off two UDP packets to the remote
+ // rtp/rtcp ports to poke a hole into the firewall for future incoming
+ // packets. We're going to send an RR/SDES RTCP packet to both of them.
+ void pokeAHole(int rtpSocket, int rtcpSocket, const AString &transport) {
+ AString source;
+ AString server_port;
+ if (!GetAttribute(transport.c_str(),
+ "source",
+ &source)
+ || !GetAttribute(transport.c_str(),
+ "server_port",
+ &server_port)) {
+ return;
+ }
+
+ int rtpPort, rtcpPort;
+ if (sscanf(server_port.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2
+ || rtpPort <= 0 || rtpPort > 65535
+ || rtcpPort <=0 || rtcpPort > 65535
+ || rtcpPort != rtpPort + 1
+ || (rtpPort & 1) != 0) {
+ return;
+ }
+
+ struct sockaddr_in addr;
+ memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(source.c_str());
+
+ if (addr.sin_addr.s_addr == INADDR_NONE) {
+ return;
+ }
+
+ // Make up an RR/SDES RTCP packet.
+ sp<ABuffer> buf = new ABuffer(65536);
+ buf->setRange(0, 0);
+ addRR(buf);
+ addSDES(rtpSocket, buf);
+
+ addr.sin_port = htons(rtpPort);
+
+ ssize_t n = sendto(
+ rtpSocket, buf->data(), buf->size(), 0,
+ (const sockaddr *)&addr, sizeof(addr));
+ CHECK_EQ(n, (ssize_t)buf->size());
+
+ addr.sin_port = htons(rtcpPort);
+
+ n = sendto(
+ rtcpSocket, buf->data(), buf->size(), 0,
+ (const sockaddr *)&addr, sizeof(addr));
+ CHECK_EQ(n, (ssize_t)buf->size());
+
+ LOGV("successfully poked holes.");
+ }
+
virtual void onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case 'conn':
@@ -282,6 +427,17 @@
sp<AMessage> notify = new AMessage('accu', id());
notify->setSize("track-index", trackIndex);
+ i = response->mHeaders.indexOfKey("transport");
+ CHECK_GE(i, 0);
+
+ if (!track->mUsingInterleavedTCP) {
+ AString transport = response->mHeaders.valueAt(i);
+
+ pokeAHole(track->mRTPSocket,
+ track->mRTCPSocket,
+ transport);
+ }
+
mRTPConn->addStream(
track->mRTPSocket, track->mRTCPSocket,
mSessionDesc, index,
@@ -378,6 +534,7 @@
mFirstAccessUnitNTP = 0;
mNumAccessUnitsReceived = 0;
mReceivedFirstRTCPPacket = false;
+ mSeekable = false;
sp<AMessage> reply = new AMessage('tear', id());
@@ -434,6 +591,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 +618,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 +707,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 +726,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 +743,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 +772,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 +791,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 +815,11 @@
}
mSeekPending = false;
+
+ sp<AMessage> doneMsg;
+ CHECK(msg->findMessage("doneMsg", &doneMsg));
+
+ doneMsg->post();
break;
}
@@ -674,6 +861,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 +890,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 +955,8 @@
++n;
}
+
+ mSeekable = true;
}
sp<APacketSource> getPacketSource(size_t index) {
@@ -783,8 +985,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/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index e088417..4e2f1e3 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -38,9 +38,11 @@
import android.text.format.Time;
import android.util.EventLog;
import android.util.Slog;
+import android.util.TimeUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
@@ -456,25 +458,28 @@
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
if (mRtcWakeupAlarms.size() > 0 || mRtcAlarms.size() > 0) {
+ final long now = System.currentTimeMillis();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
pw.println(" ");
pw.print(" Realtime wakeup (now=");
- pw.print(System.currentTimeMillis()); pw.println("):");
+ pw.print(sdf.format(new Date(now))); pw.println("):");
if (mRtcWakeupAlarms.size() > 0) {
- dumpAlarmList(pw, mRtcWakeupAlarms, " ", "RTC_WAKEUP");
+ dumpAlarmList(pw, mRtcWakeupAlarms, " ", "RTC_WAKEUP", now);
}
if (mRtcAlarms.size() > 0) {
- dumpAlarmList(pw, mRtcAlarms, " ", "RTC");
+ dumpAlarmList(pw, mRtcAlarms, " ", "RTC", now);
}
}
if (mElapsedRealtimeWakeupAlarms.size() > 0 || mElapsedRealtimeAlarms.size() > 0) {
+ final long now = SystemClock.elapsedRealtime();
pw.println(" ");
pw.print(" Elapsed realtime wakeup (now=");
- pw.print(SystemClock.elapsedRealtime()); pw.println("):");
+ TimeUtils.formatDuration(now, pw); pw.println("):");
if (mElapsedRealtimeWakeupAlarms.size() > 0) {
- dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, " ", "ELAPSED_WAKEUP");
+ dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, " ", "ELAPSED_WAKEUP", now);
}
if (mElapsedRealtimeAlarms.size() > 0) {
- dumpAlarmList(pw, mElapsedRealtimeAlarms, " ", "ELAPSED");
+ dumpAlarmList(pw, mElapsedRealtimeAlarms, " ", "ELAPSED", now);
}
}
@@ -499,12 +504,13 @@
}
}
- private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, String prefix, String label) {
+ private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
+ String prefix, String label, long now) {
for (int i=list.size()-1; i>=0; i--) {
Alarm a = list.get(i);
pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
pw.print(": "); pw.println(a);
- a.dump(pw, prefix + " ");
+ a.dump(pw, prefix + " ", now);
}
}
@@ -619,10 +625,9 @@
return sb.toString();
}
- public void dump(PrintWriter pw, String prefix)
- {
+ public void dump(PrintWriter pw, String prefix, long now) {
pw.print(prefix); pw.print("type="); pw.print(type);
- pw.print(" when="); pw.print(when);
+ pw.print(" when="); TimeUtils.formatDuration(when, now, pw);
pw.print(" repeatInterval="); pw.print(repeatInterval);
pw.print(" count="); pw.println(count);
pw.print(prefix); pw.print("operation="); pw.println(operation);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 8114be9..a663c3a 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -1038,10 +1038,8 @@
if (mNetAttributes[checkType] == null) continue;
if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE &&
noMobileData) {
- if (DBG) {
- Slog.d(TAG, "not failing over to mobile type " + checkType +
- " because Mobile Data Disabled");
- }
+ Slog.e(TAG, "not failing over to mobile type " + checkType +
+ " because Mobile Data Disabled");
continue;
}
if (mNetAttributes[checkType].isDefault()) {
@@ -1089,6 +1087,8 @@
newNet = null; // not officially avail.. try anyway, but
// report no failover
}
+ } else {
+ Slog.e(TAG, "Network failover failing.");
}
}
@@ -1131,16 +1131,13 @@
String reason = info.getReason();
String extraInfo = info.getExtraInfo();
- if (DBG) {
- String reasonText;
- if (reason == null) {
- reasonText = ".";
- } else {
- reasonText = " (" + reason + ").";
- }
- Slog.v(TAG, "Attempt to connect to " + info.getTypeName() +
- " failed" + reasonText);
+ String reasonText;
+ if (reason == null) {
+ reasonText = ".";
+ } else {
+ reasonText = " (" + reason + ").";
}
+ Slog.e(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
@@ -1164,9 +1161,9 @@
if (newNet != null) {
NetworkInfo switchTo = newNet.getNetworkInfo();
if (!switchTo.isConnected()) {
- // if the other net is connected they've already reset this and perhaps even gotten
- // a positive report we don't want to overwrite, but if not we need to clear this now
- // to turn our cellular sig strength white
+ // if the other net is connected they've already reset this and perhaps
+ // even gotten a positive report we don't want to overwrite, but if not
+ // we need to clear this now to turn our cellular sig strength white
mDefaultInetConditionPublished = 0;
}
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index 29ca9a4..ed1243a 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -49,6 +49,8 @@
public class InputManager {
static final String TAG = "InputManager";
+ private static final boolean DEBUG = false;
+
private final Callbacks mCallbacks;
private final Context mContext;
private final WindowManagerService mWindowManagerService;
@@ -129,7 +131,9 @@
throw new IllegalArgumentException("Invalid display id or dimensions.");
}
- Slog.i(TAG, "Setting display #" + displayId + " size to " + width + "x" + height);
+ if (DEBUG) {
+ Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height);
+ }
nativeSetDisplaySize(displayId, width, height);
}
@@ -138,7 +142,9 @@
throw new IllegalArgumentException("Invalid rotation.");
}
- Slog.i(TAG, "Setting display #" + displayId + " orientation to " + rotation);
+ if (DEBUG) {
+ Slog.d(TAG, "Setting display #" + displayId + " orientation to " + rotation);
+ }
nativeSetDisplayOrientation(displayId, rotation);
}
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 8e5cdc2..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>();
@@ -170,8 +167,6 @@
this.token = token;
this.callerUid = callerUid;
mounted = false;
-
- getBinder().linkToDeath(this, 0);
}
// OBB source filename
@@ -196,7 +191,11 @@
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
}
- public void cleanUp() {
+ public void link() throws RemoteException {
+ getBinder().linkToDeath(this, 0);
+ }
+
+ public void unlink() {
getBinder().unlinkToDeath(this, 0);
}
@@ -224,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
@@ -1589,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);
}
@@ -1659,56 +1651,61 @@
Slog.i(TAG, "Send to OBB handler: " + action.toString());
}
- private void addObbState(ObbState obbState) {
+ private void addObbState(ObbState obbState) throws RemoteException {
synchronized (mObbMounts) {
- List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
+ final IBinder binder = obbState.getBinder();
+ List<ObbState> obbStates = mObbMounts.get(binder);
+ final boolean unique;
+
if (obbStates == null) {
obbStates = new ArrayList<ObbState>();
- mObbMounts.put(obbState.getBinder(), obbStates);
- }
- obbStates.add(obbState);
- 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);
+ mObbMounts.put(binder, obbStates);
+ unique = true;
} else {
- mObbUidUsage.put(uid, uidUsage + 1);
+ unique = obbStates.contains(obbState);
}
+
+ if (unique) {
+ obbStates.add(obbState);
+ try {
+ obbState.link();
+ } catch (RemoteException e) {
+ /*
+ * The binder died before we could link it, so clean up our
+ * state and return failure.
+ */
+ obbStates.remove(obbState);
+ if (obbStates.isEmpty()) {
+ mObbMounts.remove(binder);
+ }
+
+ // Rethrow the error so mountObb can get it
+ throw e;
+ }
+ }
+
+ mObbPathToStateMap.put(obbState.filename, obbState);
}
}
private void removeObbState(ObbState obbState) {
synchronized (mObbMounts) {
- final List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
+ final IBinder binder = obbState.getBinder();
+ final List<ObbState> obbStates = mObbMounts.get(binder);
if (obbStates != null) {
- obbStates.remove(obbState);
- }
- if (obbStates == null || obbStates.isEmpty()) {
- mObbMounts.remove(obbState.getBinder());
- obbState.cleanUp();
- }
- 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);
+ if (obbStates.remove(obbState)) {
+ obbState.unlink();
+ }
+ if (obbStates.isEmpty()) {
+ mObbMounts.remove(binder);
}
}
+
+ mObbPathToStateMap.remove(obbState.filename);
}
}
- private void replaceObbState(ObbState oldObbState, ObbState newObbState) {
+ private void replaceObbState(ObbState oldObbState, ObbState newObbState) throws RemoteException {
synchronized (mObbMounts) {
removeObbState(oldObbState);
addObbState(newObbState);
@@ -1717,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);
@@ -1727,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());
@@ -1743,15 +1740,9 @@
action.handleError();
return;
}
-
- mActions.add(action);
- break;
}
- // Once we bind to the service, the first
- // pending request will be processed.
mActions.add(action);
- mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
break;
}
case OBB_MCS_BOUND: {
@@ -1769,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);
}
@@ -1817,12 +1808,6 @@
}
break;
}
- case OBB_MCS_GIVE_UP: {
- if (DEBUG_OBB)
- Slog.i(TAG, "OBB_MCS_GIVE_UP");
- mActions.remove(0);
- break;
- }
}
}
@@ -1862,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 {
@@ -1879,6 +1864,7 @@
if (DEBUG_OBB)
Slog.d(TAG, "Error handling OBB action", e);
handleError();
+ mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
}
}
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/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index bac21b1..b268efa 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -84,8 +84,8 @@
pw.print(prefix); pw.print("extras: "); pw.println(bundle.toString());
}
}
- pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.println(" ");
- pw.println(callerApp != null ? callerApp.toShortString() : "null");
+ pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.print(" ");
+ pw.print(callerApp != null ? callerApp.toShortString() : "null");
pw.print(" pid="); pw.print(callingPid);
pw.print(" uid="); pw.println(callingUid);
if (requiredPermission != null) {
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..f9c1679 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -493,6 +493,11 @@
+ " info: " + info);
}
+ if (info != null) {
+ native_update_network_state(info.isConnected(), info.getType(),
+ info.isRoaming(), info.getExtraInfo());
+ }
+
if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL
&& mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
String apnName = info.getExtraInfo();
@@ -1228,6 +1233,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 +1256,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 +1265,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;
}
}
@@ -1599,4 +1606,7 @@
private native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
int lac, int cid);
private native void native_agps_set_id(int type, String setid);
+
+ private native void native_update_network_state(boolean connected, int type,
+ boolean roaming, String extraInfo);
}
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_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 8e7be44..18037e4 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -182,11 +182,6 @@
virtual bool getDisplayInfo(int32_t displayId,
int32_t* width, int32_t* height, int32_t* orientation);
- virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
- bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags);
- virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
- uint32_t& policyFlags);
- virtual int32_t interceptGeneric(nsecs_t when, uint32_t& policyFlags);
virtual bool filterTouchEvents();
virtual bool filterJumpyTouchEvents();
virtual void getVirtualKeyDefinitions(const String8& deviceName,
@@ -204,8 +199,14 @@
virtual nsecs_t getKeyRepeatTimeout();
virtual nsecs_t getKeyRepeatDelay();
virtual int32_t getMaxEventsPerSecond();
+ virtual void interceptKeyBeforeQueueing(nsecs_t when, int32_t deviceId,
+ int32_t action, int32_t& flags, int32_t keyCode, int32_t scanCode,
+ uint32_t& policyFlags);
+ virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
const KeyEvent* keyEvent, uint32_t policyFlags);
+ virtual void notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
+ uint32_t policyFlags);
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
virtual bool checkInjectEventsPermissionNonReentrant(
int32_t injectorPid, int32_t injectorUid);
@@ -461,18 +462,25 @@
return android_server_PowerManagerService_isScreenBright();
}
-int32_t NativeInputManager::interceptKey(nsecs_t when,
- int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) {
-#if DEBUG_INPUT_READER_POLICY
- LOGD("interceptKey - when=%lld, deviceId=%d, down=%d, keyCode=%d, scanCode=%d, "
- "policyFlags=0x%x",
- when, deviceId, down, keyCode, scanCode, policyFlags);
+void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when,
+ int32_t deviceId, int32_t action, int32_t &flags,
+ int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("interceptKeyBeforeQueueing - when=%lld, deviceId=%d, action=%d, flags=%d, "
+ "keyCode=%d, scanCode=%d, policyFlags=0x%x",
+ when, deviceId, action, flags, keyCode, scanCode, policyFlags);
#endif
- if (down && (policyFlags & POLICY_FLAG_VIRTUAL)) {
- JNIEnv* env = jniEnv();
- env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.virtualKeyDownFeedback);
- checkAndClearExceptionFromCallback(env, "virtualKeyDownFeedback");
+ bool down = action == AKEY_EVENT_ACTION_DOWN;
+ if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+
+ if (down) {
+ JNIEnv* env = jniEnv();
+ env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.virtualKeyDownFeedback);
+ checkAndClearExceptionFromCallback(env, "virtualKeyDownFeedback");
+ }
}
const int32_t WM_ACTION_PASS_TO_USER = 1;
@@ -496,10 +504,10 @@
wmActions = WM_ACTION_PASS_TO_USER;
}
- int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
if (! isScreenOn) {
// Key presses and releases wake the device.
policyFlags |= POLICY_FLAG_WOKE_HERE;
+ flags |= AKEY_EVENT_FLAG_WOKE_HERE;
}
if (! isScreenBright) {
@@ -516,36 +524,31 @@
}
if (wmActions & WM_ACTION_PASS_TO_USER) {
- actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
+ policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
-
- return actions;
}
-int32_t NativeInputManager::interceptGeneric(nsecs_t when, uint32_t& policyFlags) {
-#if DEBUG_INPUT_READER_POLICY
- LOGD("interceptGeneric - when=%lld, policyFlags=0x%x", when, policyFlags);
+void NativeInputManager::interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("interceptGenericBeforeQueueing - when=%lld, policyFlags=0x%x", when, policyFlags);
#endif
- int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
if (isScreenOn()) {
// Only dispatch events when the device is awake.
// Do not wake the device.
- actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
+ policyFlags |= POLICY_FLAG_PASS_TO_USER;
if (! isScreenBright()) {
// Brighten the screen if dimmed.
policyFlags |= POLICY_FLAG_BRIGHT_HERE;
}
}
-
- return actions;
}
-int32_t NativeInputManager::interceptSwitch(nsecs_t when, int32_t switchCode,
- int32_t switchValue, uint32_t& policyFlags) {
-#if DEBUG_INPUT_READER_POLICY
- LOGD("interceptSwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x",
+void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
+ int32_t switchValue, uint32_t policyFlags) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("notifySwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x",
when, switchCode, switchValue, policyFlags);
#endif
@@ -558,8 +561,6 @@
checkAndClearExceptionFromCallback(env, "notifyLidSwitchChanged");
break;
}
-
- return InputReaderPolicyInterface::ACTION_NONE;
}
bool NativeInputManager::filterTouchEvents() {
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index 71c7aba..43e8467 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -238,45 +238,57 @@
return interface;
}
-static const AGpsInterface* GetAGpsInterface()
-{
- if (!sGpsInterface)
+static const GpsInterface* GetGpsInterface(JNIEnv* env, jobject obj) {
+ // this must be set before calling into the HAL library
+ if (!mCallbacksObj)
+ mCallbacksObj = env->NewGlobalRef(obj);
+
+ if (!sGpsInterface) {
sGpsInterface = get_gps_interface();
- if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+ if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0) {
+ sGpsInterface = NULL;
+ return NULL;
+ }
+ }
+ return sGpsInterface;
+}
+
+static const AGpsInterface* GetAGpsInterface(JNIEnv* env, jobject obj)
+{
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ 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);
}
return sAGpsInterface;
}
-static const GpsNiInterface* GetNiInterface()
+static const GpsNiInterface* GetNiInterface(JNIEnv* env, jobject obj)
{
- if (!sGpsInterface)
- sGpsInterface = get_gps_interface();
- if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ 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);
}
return sGpsNiInterface;
}
-static const AGpsRilInterface* GetAGpsRilInterface()
+static const AGpsRilInterface* GetAGpsRilInterface(JNIEnv* env, jobject obj)
{
- if (!sGpsInterface)
- sGpsInterface = get_gps_interface();
- if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ 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,53 +309,62 @@
}
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)
{
- // this must be set before calling into the HAL library
- if (!mCallbacksObj)
- mCallbacksObj = env->NewGlobalRef(obj);
-
- if (!sGpsInterface)
- sGpsInterface = get_gps_interface();
- if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ 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;
}
static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject obj)
{
- sGpsInterface->cleanup();
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (interface)
+ interface->cleanup();
}
static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* env, jobject obj,
jint mode, jint recurrence, jint min_interval, jint preferred_accuracy, jint preferred_time)
{
- return (sGpsInterface->set_position_mode(mode, recurrence, min_interval, preferred_accuracy,
- preferred_time) == 0);
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (interface)
+ return (interface->set_position_mode(mode, recurrence, min_interval, preferred_accuracy,
+ preferred_time) == 0);
+ else
+ return false;
}
static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj)
{
- return (sGpsInterface->start() == 0);
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (interface)
+ return (interface->start() == 0);
+ else
+ return false;
}
static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject obj)
{
- return (sGpsInterface->stop() == 0);
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (interface)
+ return (interface->stop() == 0);
+ else
+ return false;
}
static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* env, jobject obj, jint flags)
{
- sGpsInterface->delete_aiding_data(flags);
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (interface)
+ interface->delete_aiding_data(flags);
}
static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject obj,
@@ -381,7 +402,7 @@
jobject obj, jint type, jint mcc, jint mnc, jint lac, jint cid)
{
AGpsRefLocation location;
- const AGpsRilInterface* interface = GetAGpsRilInterface();
+ const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj);
if (!interface) {
LOGE("no AGPS RIL interface in agps_set_reference_location_cellid");
return;
@@ -408,7 +429,7 @@
jobject obj, jbyteArray ni_msg, jint size)
{
size_t sz;
- const AGpsRilInterface* interface = GetAGpsRilInterface();
+ const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj);
if (!interface) {
LOGE("no AGPS RIL interface in send_ni_message");
return;
@@ -424,7 +445,7 @@
static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env,
jobject obj, jint type, jstring setid_string)
{
- const AGpsRilInterface* interface = GetAGpsRilInterface();
+ const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj);
if (!interface) {
LOGE("no AGPS RIL interface in agps_set_id");
return;
@@ -451,19 +472,26 @@
static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj,
jlong time, jlong timeReference, jint uncertainty)
{
- sGpsInterface->inject_time(time, timeReference, uncertainty);
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (interface)
+ interface->inject_time(time, timeReference, uncertainty);
}
static void android_location_GpsLocationProvider_inject_location(JNIEnv* env, jobject obj,
jdouble latitude, jdouble longitude, jfloat accuracy)
{
- sGpsInterface->inject_location(latitude, longitude, accuracy);
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (interface)
+ interface->inject_location(latitude, longitude, accuracy);
}
static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, jobject obj)
{
if (!sGpsXtraInterface) {
- sGpsXtraInterface = (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE);
+ const GpsInterface* interface = GetGpsInterface(env, obj);
+ if (!interface)
+ return false;
+ sGpsXtraInterface = (const GpsXtraInterface*)interface->get_extension(GPS_XTRA_INTERFACE);
if (sGpsXtraInterface) {
int result = sGpsXtraInterface->init(&sGpsXtraCallbacks);
if (result) {
@@ -485,7 +513,7 @@
static void android_location_GpsLocationProvider_agps_data_conn_open(JNIEnv* env, jobject obj, jstring apn)
{
- const AGpsInterface* interface = GetAGpsInterface();
+ const AGpsInterface* interface = GetAGpsInterface(env, obj);
if (!interface) {
LOGE("no AGPS interface in agps_data_conn_open");
return;
@@ -501,7 +529,7 @@
static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* env, jobject obj)
{
- const AGpsInterface* interface = GetAGpsInterface();
+ const AGpsInterface* interface = GetAGpsInterface(env, obj);
if (!interface) {
LOGE("no AGPS interface in agps_data_conn_open");
return;
@@ -511,7 +539,7 @@
static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* env, jobject obj)
{
- const AGpsInterface* interface = GetAGpsInterface();
+ const AGpsInterface* interface = GetAGpsInterface(env, obj);
if (!interface) {
LOGE("no AGPS interface in agps_data_conn_open");
return;
@@ -522,7 +550,7 @@
static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject obj,
jint type, jstring hostname, jint port)
{
- const AGpsInterface* interface = GetAGpsInterface();
+ const AGpsInterface* interface = GetAGpsInterface(env, obj);
if (!interface) {
LOGE("no AGPS interface in agps_data_conn_open");
return;
@@ -535,7 +563,7 @@
static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
jint notifId, jint response)
{
- const GpsNiInterface* interface = GetNiInterface();
+ const GpsNiInterface* interface = GetNiInterface(env, obj);
if (!interface) {
LOGE("no NI interface in send_ni_response");
return;
@@ -558,6 +586,23 @@
return result;
}
+static void android_location_GpsLocationProvider_update_network_state(JNIEnv* env, jobject obj,
+ jboolean connected, int type, jboolean roaming, jstring extraInfo)
+{
+ const AGpsRilInterface* interface = GetAGpsRilInterface(env, obj);
+ if (interface &&
+ (interface->size > ((char *)&interface->update_network_state - (char *)&interface)) &&
+ interface->update_network_state) {
+ if (extraInfo) {
+ const char *extraInfoStr = env->GetStringUTFChars(extraInfo, NULL);
+ interface->update_network_state(connected, type, roaming, extraInfoStr);
+ env->ReleaseStringUTFChars(extraInfo, extraInfoStr);
+ } else {
+ interface->update_network_state(connected, type, roaming, NULL);
+ }
+ }
+}
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
@@ -583,6 +628,7 @@
{"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response},
{"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message},
{"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
+ {"native_update_network_state", "(ZIZLjava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state },
};
int register_android_server_location_GpsLocationProvider(JNIEnv* env)
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index b4a3c95..5542c42 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1649,12 +1649,19 @@
}
/**
+ * Determines if the specified number is actually a URI
+ * (i.e. a SIP address) rather than a regular PSTN phone number,
+ * based on whether or not the number contains an "@" character.
+ *
* @hide
* @param number
* @return true if number contains @
*/
public static boolean isUriNumber(String number) {
- return number != null && number.contains("@");
+ // Note we allow either "@" or "%40" to indicate a URI, in case
+ // the passed-in string is URI-escaped. (Neither "@" nor "%40"
+ // will ever be found in a legal PSTN number.)
+ return number != null && (number.contains("@") || number.contains("%40"));
}
/**
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/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index cf89848..360d35e 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -26,9 +26,9 @@
import android.text.TextUtils;
import android.telephony.TelephonyManager;
import android.telephony.PhoneNumberUtils;
-import android.util.Config;
import android.util.Log;
+
/**
* Looks up caller information for the given phone number.
*
@@ -36,6 +36,7 @@
*/
public class CallerInfo {
private static final String TAG = "CallerInfo";
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
public static final String UNKNOWN_NUMBER = "-1";
public static final String PRIVATE_NUMBER = "-2";
@@ -128,7 +129,7 @@
info.isCachedPhotoCurrent = false;
info.contactExists = false;
- if (Config.LOGV) Log.v(TAG, "construct callerInfo from cursor");
+ if (VDBG) Log.v(TAG, "construct callerInfo from cursor");
if (cursor != null) {
if (cursor.moveToFirst()) {
@@ -166,31 +167,30 @@
// Look for the person ID.
// TODO: This is pretty ugly now, see bug 2269240 for
- // more details. With tel: URI the contact id is in
- // col "_id" while when we use a
- // content://contacts/data/phones URI, the contact id
- // is col "contact_id". As a work around we use the
- // type of the contact url to figure out which column
- // we should look at to get the contact_id.
-
- final String mimeType = context.getContentResolver().getType(contactRef);
+ // more details. The column to use depends upon the type of URL,
+ // for content://com.android.contacts/data/phones the "contact_id"
+ // column is used. For content/com.andriod.contacts/phone_lookup"
+ // the "_ID" column is used. If it is neither we leave columnIndex
+ // at -1 and no person ID will be available.
columnIndex = -1;
- if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
- // content://com.android.contacts/data/phones URL
+ String url = contactRef.toString();
+ if (url.startsWith("content://com.android.contacts/data/phones")) {
+ if (VDBG) Log.v(TAG,
+ "URL path starts with 'data/phones' using RawContacts.CONTACT_ID");
columnIndex = cursor.getColumnIndex(RawContacts.CONTACT_ID);
- } else {
- // content://com.android.contacts/phone_lookup URL
- // TODO: mime type is null here so we cannot test
- // if we have the right url type. phone_lookup URL
- // should resolve to a mime type.
+ } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
+ if (VDBG) Log.v(TAG,
+ "URL path starts with 'phone_lookup' using PhoneLookup._ID");
columnIndex = cursor.getColumnIndex(PhoneLookup._ID);
+ } else {
+ Log.e(TAG, "Bad contact URL '" + url + "'");
}
if (columnIndex != -1) {
info.person_id = cursor.getLong(columnIndex);
} else {
- Log.e(TAG, "Column missing for " + contactRef);
+ Log.e(TAG, "person_id column missing for " + contactRef);
}
// look for the custom ringtone, create from the string stored
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/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 02af0ed..67f13bd 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.net.rtp.AudioGroup;
-import android.net.rtp.AudioStream;
import android.net.sip.SipAudioCall;
import android.net.sip.SipErrorCode;
import android.net.sip.SipException;
@@ -29,11 +28,9 @@
import android.os.Message;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.telephony.Call;
-import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
@@ -382,40 +379,6 @@
}
}
- private CallerInfo createCallerInfo(String number, SipProfile callee) {
- SipProfile p = callee;
- String name = p.getDisplayName();
- if (TextUtils.isEmpty(name)) name = p.getUserName();
- CallerInfo info = new CallerInfo();
- info.name = name;
- info.phoneNumber = number;
- if (DEBUG) {
- Log.d(LOG_TAG, "create caller info from scratch:");
- Log.d(LOG_TAG, " name: " + info.name);
- Log.d(LOG_TAG, " numb: " + info.phoneNumber);
- }
- return info;
- }
-
- // from contacts
- private CallerInfo findCallerInfo(String number) {
- CallerInfo info = CallerInfo.getCallerInfo(mContext, number);
- if ((info == null) || (info.name == null)) return null;
- if (DEBUG) {
- Log.d(LOG_TAG, "got caller info from contact:");
- Log.d(LOG_TAG, " name: " + info.name);
- Log.d(LOG_TAG, " numb: " + info.phoneNumber);
- Log.d(LOG_TAG, " pres: " + info.numberPresentation);
- }
- return info;
- }
-
- private CallerInfo getCallerInfo(String number, SipProfile callee) {
- CallerInfo info = findCallerInfo(number);
- if (info == null) info = createCallerInfo(number, callee);
- return info;
- }
-
Connection dial(String originalNumber) throws SipException {
String calleeSipUri = originalNumber;
if (!calleeSipUri.contains("@")) {
@@ -424,8 +387,7 @@
try {
SipProfile callee =
new SipProfile.Builder(calleeSipUri).build();
- CallerInfo info = getCallerInfo(originalNumber, callee);
- SipConnection c = new SipConnection(this, callee, info);
+ SipConnection c = new SipConnection(this, callee);
connections.add(c);
c.dial();
setState(Call.State.DIALING);
@@ -461,10 +423,7 @@
void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
SipProfile callee = sipAudioCall.getPeerProfile();
- CallerInfo info = findCallerInfo(getUriString(callee));
- if (info == null) info = findCallerInfo(callee.getUserName());
- if (info == null) info = findCallerInfo(callee.getDisplayName());
- SipConnection c = new SipConnection(this, callee, info);
+ SipConnection c = new SipConnection(this, callee);
connections.add(c);
Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
@@ -700,12 +659,10 @@
}
};
- public SipConnection(SipCall owner, SipProfile callee,
- CallerInfo info) {
+ public SipConnection(SipCall owner, SipProfile callee) {
super(getUriString(callee));
mOwner = owner;
mPeer = callee;
- setUserData(info);
}
void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
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..99f0abe
--- /dev/null
+++ b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
@@ -0,0 +1,218 @@
+/*
+ * 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;
+ }
+
+ /**
+ * Start an incoming call for the given phone number
+ *
+ * @param phoneNumber is the number to show as incoming call
+ * @return true if the incoming call is started successfully, false if it fails.
+ */
+ public boolean startIncomingCall(String phoneNumber) {
+ RilCtrlCmds.CtrlReqSetMTCall req = new RilCtrlCmds.CtrlReqSetMTCall();
+
+ 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;
+ }
+
+ /**
+ * Hang up a connection remotelly for the given call fail cause
+ *
+ * @param connectionID is the connection to be hung up
+ * @param failCause is the call fail cause defined in ril.h
+ * @return true if the hangup is successful, false if it fails
+ */
+ public boolean hangupRemote(int connectionId, int failCause) {
+ RilCtrlCmds.CtrlHangupConnRemote req = new RilCtrlCmds.CtrlHangupConnRemote();
+ req.setConnectionId(connectionId);
+ req.setCallFailCause(failCause);
+
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, req)) {
+ Log.v(TAG, "send CTRL_CMD_HANGUP_CONN_REMOTE request failed");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set call transition flag to the Mock Ril
+ *
+ * @param flag is a boolean value for the call transiton flag
+ * true: call transition: dialing->alert, alert->active is controlled
+ * false: call transition is automatically handled by Mock Ril
+ * @return true if the request is successful, false if it failed to set the flag
+ */
+ public boolean setCallTransitionFlag(boolean flag) {
+ RilCtrlCmds.CtrlSetCallTransitionFlag req = new RilCtrlCmds.CtrlSetCallTransitionFlag();
+
+ req.setFlag(flag);
+
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, req)) {
+ Log.v(TAG, "send CTRL_CMD_SET_CALL_TRANSITION_FLAG request failed");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the dialing call to alert if the call transition flag is true
+ *
+ * @return true if the call transition is successful, false if it fails
+ */
+ public boolean setDialCallToAlert() {
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_ALERT, 0, 0, null)) {
+ Log.v(TAG, "send CTRL_CMD_SET_CALL_ALERT request failed");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the alert call to active if the call transition flag is true
+ *
+ * @return true if the call transition is successful, false if it fails
+ */
+ public boolean setAlertCallToActive() {
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_ACTIVE, 0, 0, null)) {
+ Log.v(TAG, "send CTRL_CMD_SET_CALL_ACTIVE 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..3149ee1
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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 Case 1: 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 case 2: 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 case 3: 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");
+ }
+
+ /**
+ * Test case 3: test get radio state
+ */
+ 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");
+ }
+
+ /**
+ * Test case 5: test set radio state
+ */
+ 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);
+ log("get response status :" + resp.getStatus());
+ log("get response for command: " + resp.getCmd());
+ log("get command token: " + resp.getToken());
+
+ 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);
+ }
+
+ /**
+ * Test case 6: test start incoming call and hangup it.
+ */
+ public void testStartIncomingCallAndHangup() throws IOException {
+ log("testStartIncomingCallAndHangup");
+ RilCtrlCmds.CtrlReqSetMTCall cmd = new RilCtrlCmds.CtrlReqSetMTCall();
+ String incomingCall = "6502889108";
+ // set the MT call
+ cmd.setPhoneNumber(incomingCall);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_MT_CALL, 0, 0, cmd);
+ // get response
+ Msg resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("The ril is not in a proper state to set MT calls.",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // allow the incoming call alerting for some time
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {}
+
+ // we are playing a trick to assume the current is 1
+ RilCtrlCmds.CtrlHangupConnRemote hangupCmd = new RilCtrlCmds.CtrlHangupConnRemote();
+ hangupCmd.setConnectionId(1);
+ hangupCmd.setCallFailCause(16); // normal hangup
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, hangupCmd);
+
+ // get response
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response for hangup connection: " + resp.getStatus());
+ assertTrue("CTRL_CMD_HANGUP_CONN_REMOTE failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+ }
+
+ /**
+ * Test case 7: test set call transition flag
+ */
+ public void testSetCallTransitionFlag() throws IOException {
+ log("testSetCallTransitionFlag");
+ // Set flag to true:
+ RilCtrlCmds.CtrlSetCallTransitionFlag cmd = new RilCtrlCmds.CtrlSetCallTransitionFlag();
+ cmd.setFlag(true);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, cmd);
+
+ Msg resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("Set call transition flag failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // add a dialing call
+ RilCtrlCmds.CtrlReqAddDialingCall cmdDialCall = new RilCtrlCmds.CtrlReqAddDialingCall();
+ String phoneNumber = "5102345678";
+ cmdDialCall.setPhoneNumber(phoneNumber);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_ADD_DIALING_CALL, 0, 0, cmdDialCall);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response status for adding a dialing call: " + resp.getStatus());
+ assertTrue("add dialing call failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {}
+
+ // send command to force call state change
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_ALERT, 0, 0, null);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("Set call alert failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {}
+
+ // send command to force call state change
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_ACTIVE, 0, 0, null);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("Set call active failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // hangup the active all remotely
+ RilCtrlCmds.CtrlHangupConnRemote hangupCmd = new RilCtrlCmds.CtrlHangupConnRemote();
+ hangupCmd.setConnectionId(1);
+ hangupCmd.setCallFailCause(16); // normal hangup
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, hangupCmd);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response for hangup connection: " + resp.getStatus());
+ assertTrue("CTRL_CMD_HANGUP_CONN_REMOTE failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // set the flag to false
+ cmd.setFlag(false);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, cmd);
+ resp = Msg.recv(mMockRilChannel);
+ assertTrue("Set call transition flag failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+ }
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 9c5fcda2..c8ba904 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -835,7 +835,9 @@
bool hasErrors = false;
if (drawables != NULL) {
- err = preProcessImages(bundle, assets, drawables);
+ if (bundle->getOutputAPKFile() != NULL) {
+ err = preProcessImages(bundle, assets, drawables);
+ }
if (err == NO_ERROR) {
err = makeFileResources(bundle, assets, &table, drawables, "drawable");
if (err != NO_ERROR) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
index 744bfbe..d5a90e6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
@@ -245,8 +245,8 @@
BridgeXmlBlockParser parser = null;
if (set instanceof BridgeXmlBlockParser) {
parser = (BridgeXmlBlockParser)set;
- } else {
- // reall this should not be happening since its instantiated in Bridge
+ } else if (set != null) { // null parser is ok
+ // really this should not be happening since its instantiated in Bridge
mLogger.error("Parser is not a BridgeXmlBlockParser!");
return null;
}
@@ -255,13 +255,16 @@
TreeMap<Integer, String> styleNameMap = searchAttrs(attrs, frameworkAttributes);
BridgeTypedArray ta = ((BridgeResources) mResources).newTypeArray(attrs.length,
- parser.isPlatformFile());
+ parser != null ? parser.isPlatformFile() : true);
// resolve the defStyleAttr value into a IStyleResourceValue
IStyleResourceValue defStyleValues = null;
// look for a custom style.
- String customStyle = parser.getAttributeValue(null /* namespace*/, "style");
+ String customStyle = null;
+ if (parser != null) {
+ customStyle = parser.getAttributeValue(null /* namespace*/, "style");
+ }
if (customStyle != null) {
IResourceValue item = findResValue(customStyle);
@@ -308,7 +311,10 @@
int index = styleAttribute.getKey().intValue();
String name = styleAttribute.getValue();
- String value = parser.getAttributeValue(namespace, name);
+ String value = null;
+ if (parser != null) {
+ value = parser.getAttributeValue(namespace, name);
+ }
// if there's no direct value for this attribute in the XML, we look for default
// values in the widget defStyle, and then in the theme.
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 80c35fb..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,13 +59,27 @@
*/
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";
/**
+ * Action to broadcast when SipService is up.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_SERVICE_UP =
+ "android.net.sip.SIP_SERVICE_UP";
+ /**
* Action string for the incoming call intent for the Phone app.
* Internal use only.
* @hide
@@ -171,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
@@ -187,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,
@@ -284,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
@@ -314,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
@@ -482,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
@@ -491,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);
}
@@ -507,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/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
index 050eddc..2514262 100644
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ b/voip/java/com/android/server/sip/SipHelper.java
@@ -238,6 +238,8 @@
ClientTransaction tid = responseEvent.getClientTransaction();
ClientTransaction ct = authenticationHelper.handleChallenge(
responseEvent.getResponse(), tid, mSipProvider, 5);
+ if (DEBUG) Log.d(TAG, "send request with challenge response: "
+ + ct.getRequest());
ct.sendRequest();
return ct;
}
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index a7c61e5..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
@@ -100,6 +101,7 @@
public static void start(Context context) {
if (SipManager.isApiSupported(context)) {
ServiceManager.addService("sip", new SipService(context));
+ context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
Log.i(TAG, "SIP service started");
}
}
@@ -110,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();
@@ -122,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()) {
@@ -133,6 +153,8 @@
}
public void open(SipProfile localProfile) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.USE_SIP, null);
localProfile.setCallingUid(Binder.getCallingUid());
try {
createGroup(localProfile);
@@ -145,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; "
@@ -158,7 +182,7 @@
incomingCallPendingIntent, listener);
if (localProfile.getAutoRegistration()) {
group.openToReceiveCalls();
- if (isWifiOn()) grabWifiLock();
+ if (isWifiActive()) grabWifiLock();
}
} catch (SipException e) {
Log.e(TAG, "openToReceiveCalls()", e);
@@ -180,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)) {
@@ -190,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)) {
@@ -205,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)) {
@@ -217,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)) {
@@ -228,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 {
@@ -240,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);
}
@@ -329,9 +365,8 @@
}
}
- private boolean isWifiOn() {
+ private boolean isWifiActive() {
return "WIFI".equalsIgnoreCase(mNetworkType);
- //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType));
}
private synchronized void onConnectivityChanged(
@@ -348,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();
}
@@ -825,11 +863,13 @@
synchronized (SipService.this) {
if (notCurrentSession(session)) return;
- if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
- if (DEBUG) Log.d(TAG, " pause auto-registration");
- stop();
- } else {
- onError();
+ switch (errorCode) {
+ case SipErrorCode.INVALID_CREDENTIALS:
+ case SipErrorCode.SERVER_UNREACHABLE:
+ if (DEBUG) Log.d(TAG, " pause auto-registration");
+ stop();
+ default:
+ restartLater();
}
mErrorCode = errorCode;
@@ -846,11 +886,11 @@
mErrorCode = SipErrorCode.TIME_OUT;
mProxy.onRegistrationTimeout(session);
- onError();
+ restartLater();
}
}
- private void onError() {
+ private void restartLater() {
mRegistered = false;
restart(backoffDuration());
if (mKeepAliveProcess != null) {
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 37fffa8..57b3710 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -96,8 +96,6 @@
private SipStack mSipStack;
private SipHelper mSipHelper;
- private String mLastNonce;
- private int mRPort;
// session that processes INVITE requests
private SipSessionImpl mCallReceiverSession;
@@ -150,7 +148,6 @@
Log.d(TAG, " start stack for " + myself.getUriString());
stack.start();
- mLastNonce = null;
mCallReceiverSession = null;
mSessionMap.clear();
}
@@ -366,8 +363,12 @@
ClientTransaction mClientTransaction;
String mPeerSessionDescription;
boolean mInCall;
- boolean mReRegisterFlag = false;
SessionTimer mTimer;
+ int mAuthenticationRetryCount;
+
+ // for registration
+ boolean mReRegisterFlag = false;
+ int mRPort;
// lightweight timer
class SessionTimer {
@@ -417,6 +418,8 @@
mState = SipSession.State.READY_TO_CALL;
mInviteReceived = null;
mPeerSessionDescription = null;
+ mRPort = 0;
+ mAuthenticationRetryCount = 0;
if (mDialog != null) mDialog.delete();
mDialog = null;
@@ -799,22 +802,10 @@
onRegistrationDone((state == SipSession.State.REGISTERING)
? getExpiryTime(((ResponseEvent) evt).getResponse())
: -1);
- mLastNonce = null;
- mRPort = 0;
return true;
case Response.UNAUTHORIZED:
case Response.PROXY_AUTHENTICATION_REQUIRED:
- if (!handleAuthentication(event)) {
- if (mLastNonce == null) {
- onRegistrationFailed(SipErrorCode.SERVER_ERROR,
- "server does not provide challenge");
- } else {
- Log.v(TAG, "Incorrect username/password");
- onRegistrationFailed(
- SipErrorCode.INVALID_CREDENTIALS,
- "incorrect username or password");
- }
- }
+ handleAuthentication(event);
return true;
default:
if (statusCode >= 500) {
@@ -830,16 +821,24 @@
throws SipException {
Response response = event.getResponse();
String nonce = getNonceFromResponse(response);
- if (((nonce != null) && nonce.equals(mLastNonce)) ||
- (nonce == null)) {
- mLastNonce = nonce;
+ if (nonce == null) {
+ onError(SipErrorCode.SERVER_ERROR,
+ "server does not provide challenge");
return false;
- } else {
+ } else if (mAuthenticationRetryCount < 2) {
mClientTransaction = mSipHelper.handleChallenge(
event, getAccountManager());
mDialog = mClientTransaction.getDialog();
- mLastNonce = nonce;
+ mAuthenticationRetryCount++;
+ if (isLoggable(this, event)) {
+ Log.d(TAG, " authentication retry count="
+ + mAuthenticationRetryCount);
+ }
return true;
+ } else {
+ onError(SipErrorCode.INVALID_CREDENTIALS,
+ "incorrect username or password");
+ return false;
}
}
@@ -995,12 +994,6 @@
getRealmFromResponse(response));
} else if (handleAuthentication(event)) {
addSipSession(this);
- } else if (mLastNonce == null) {
- onError(SipErrorCode.SERVER_ERROR,
- "server does not provide challenge");
- } else {
- onError(SipErrorCode.INVALID_CREDENTIALS,
- "incorrect username or password");
}
return true;
case Response.REQUEST_PENDING:
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.
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 9d27bde..281077c 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -862,15 +862,7 @@
mIsAnyNetworkDisabled.set(false);
requestConnectionInfo();
SupplicantState supplState = mWifiInfo.getSupplicantState();
- /**
- * The MAC address isn't going to change, so just request it
- * once here.
- */
- String macaddr = getMacAddress();
- if (macaddr != null) {
- mWifiInfo.setMacAddress(macaddr);
- }
if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant established, state=" +
supplState);
// Wi-Fi supplicant connection state changed:
@@ -1289,6 +1281,10 @@
*/
setNumAllowedChannels();
synchronized (this) {
+ String macaddr = WifiNative.getMacAddressCommand();
+ if (macaddr != null) {
+ mWifiInfo.setMacAddress(macaddr);
+ }
if (mRunState == RUN_STATE_STARTING) {
mRunState = RUN_STATE_RUNNING;
if (!mIsScanOnly) {
@@ -2010,18 +2006,6 @@
}
/**
- * Get MAC address of radio
- *
- * @return MAC address, null on failure
- */
- public synchronized String getMacAddress() {
- if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
- return null;
- }
- return WifiNative.getMacAddressCommand();
- }
-
- /**
* Start driver
*
* @return {@code true} if the operation succeeds, {@code false} otherwise