diff options
567 files changed, 17728 insertions, 3824 deletions
@@ -161,6 +161,14 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/view/IInputMethodManager.aidl \ core/java/com/android/internal/view/IInputMethodSession.aidl \ core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \ + core/java/com/trustedlogic/trustednfc/android/ILlcpConnectionlessSocket.aidl \ + core/java/com/trustedlogic/trustednfc/android/ILlcpServiceSocket.aidl \ + core/java/com/trustedlogic/trustednfc/android/ILlcpSocket.aidl \ + core/java/com/trustedlogic/trustednfc/android/INdefTag.aidl \ + core/java/com/trustedlogic/trustednfc/android/INfcManager.aidl \ + core/java/com/trustedlogic/trustednfc/android/INfcTag.aidl \ + core/java/com/trustedlogic/trustednfc/android/IP2pInitiator.aidl \ + core/java/com/trustedlogic/trustednfc/android/IP2pTarget.aidl \ location/java/android/location/ICountryDetector.aidl \ location/java/android/location/ICountryListener.aidl \ location/java/android/location/IGeocodeProvider.aidl \ diff --git a/api/current.xml b/api/current.xml index 42787e6..e3b6a01 100644 --- a/api/current.xml +++ b/api/current.xml @@ -6829,6 +6829,17 @@ visibility="public" > </field> +<field name="listDividerAlertDialog" + type="int" + transient="false" + volatile="false" + value="16843590" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="listPopupWindowStyle" type="int" transient="false" @@ -9876,6 +9887,17 @@ visibility="public" > </field> +<field name="textColorAlertDialogListItem" + type="int" + transient="false" + volatile="false" + value="16843591" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="textColorHighlight" type="int" transient="false" @@ -26663,6 +26685,17 @@ visibility="public" > </method> +<method name="getContext" + return="android.content.Context" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="setAdapter" return="android.app.AlertDialog.Builder" abstract="false" @@ -28621,6 +28654,623 @@ > </field> </class> +<class name="DownloadManager" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="enqueue" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="request" type="android.app.DownloadManager.Request"> +</parameter> +</method> +<method name="openDownloadedFile" + return="android.os.ParcelFileDescriptor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="long"> +</parameter> +<exception name="FileNotFoundException" type="java.io.FileNotFoundException"> +</exception> +</method> +<method name="query" + return="android.database.Cursor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="query" type="android.app.DownloadManager.Query"> +</parameter> +</method> +<method name="remove" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="long"> +</parameter> +</method> +<field name="ACTION_DOWNLOAD_COMPLETE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.DOWNLOAD_COMPLETE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_NOTIFICATION_CLICKED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_VIEW_DOWNLOADS" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.VIEW_DOWNLOADS"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_BYTES_DOWNLOADED_SO_FAR" + type="java.lang.String" + transient="false" + volatile="false" + value=""bytes_so_far"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_DESCRIPTION" + type="java.lang.String" + transient="false" + volatile="false" + value=""description"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_ERROR_CODE" + type="java.lang.String" + transient="false" + volatile="false" + value=""error_code"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_ID" + type="java.lang.String" + transient="false" + volatile="false" + value=""_id"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_LAST_MODIFIED_TIMESTAMP" + type="java.lang.String" + transient="false" + volatile="false" + value=""last_modified_timestamp"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_LOCAL_URI" + type="java.lang.String" + transient="false" + volatile="false" + value=""local_uri"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_MEDIA_TYPE" + type="java.lang.String" + transient="false" + volatile="false" + value=""media_type"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_STATUS" + type="java.lang.String" + transient="false" + volatile="false" + value=""status"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_TITLE" + type="java.lang.String" + transient="false" + volatile="false" + value=""title"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_TOTAL_SIZE_BYTES" + type="java.lang.String" + transient="false" + volatile="false" + value=""total_size"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_URI" + type="java.lang.String" + transient="false" + volatile="false" + value=""uri"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_CANNOT_RESUME" + type="int" + transient="false" + volatile="false" + value="1008" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_DEVICE_NOT_FOUND" + type="int" + transient="false" + volatile="false" + value="1007" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_FILE_ALREADY_EXISTS" + type="int" + transient="false" + volatile="false" + value="1009" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_FILE_ERROR" + type="int" + transient="false" + volatile="false" + value="1001" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_HTTP_DATA_ERROR" + type="int" + transient="false" + volatile="false" + value="1004" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_INSUFFICIENT_SPACE" + type="int" + transient="false" + volatile="false" + value="1006" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_TOO_MANY_REDIRECTS" + type="int" + transient="false" + volatile="false" + value="1005" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_UNHANDLED_HTTP_CODE" + type="int" + transient="false" + volatile="false" + value="1002" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_UNKNOWN" + type="int" + transient="false" + volatile="false" + value="1000" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="EXTRA_DOWNLOAD_ID" + type="java.lang.String" + transient="false" + volatile="false" + value=""extra_download_id"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_FAILED" + type="int" + transient="false" + volatile="false" + value="16" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_PAUSED" + type="int" + transient="false" + volatile="false" + value="4" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_PENDING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_RUNNING" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_SUCCESSFUL" + type="int" + transient="false" + volatile="false" + value="8" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="DownloadManager.Query" + extends="java.lang.Object" + abstract="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="DownloadManager.Query" + type="android.app.DownloadManager.Query" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="setFilterById" + return="android.app.DownloadManager.Query" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="long"> +</parameter> +</method> +<method name="setFilterByStatus" + return="android.app.DownloadManager.Query" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="flags" type="int"> +</parameter> +</method> +</class> +<class name="DownloadManager.Request" + extends="java.lang.Object" + abstract="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="DownloadManager.Request" + type="android.app.DownloadManager.Request" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</constructor> +<method name="addRequestHeader" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="java.lang.String"> +</parameter> +<parameter name="value" type="java.lang.String"> +</parameter> +</method> +<method name="setAllowedNetworkTypes" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="flags" type="int"> +</parameter> +</method> +<method name="setAllowedOverRoaming" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="allowed" type="boolean"> +</parameter> +</method> +<method name="setDescription" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="description" type="java.lang.CharSequence"> +</parameter> +</method> +<method name="setDestinationInExternalFilesDir" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="dirType" type="java.lang.String"> +</parameter> +<parameter name="subPath" type="java.lang.String"> +</parameter> +</method> +<method name="setDestinationInExternalPublicDir" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dirType" type="java.lang.String"> +</parameter> +<parameter name="subPath" type="java.lang.String"> +</parameter> +</method> +<method name="setDestinationUri" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</method> +<method name="setMimeType" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="mimeType" type="java.lang.String"> +</parameter> +</method> +<method name="setShowRunningNotification" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="show" type="boolean"> +</parameter> +</method> +<method name="setTitle" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="title" type="java.lang.CharSequence"> +</parameter> +</method> +<method name="setVisibleInDownloadsUi" + return="android.app.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="isVisible" type="boolean"> +</parameter> +</method> +<field name="NETWORK_MOBILE" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="NETWORK_WIFI" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="ExpandableListActivity" extends="android.app.Activity" abstract="false" @@ -38025,6 +38675,111 @@ </package> <package name="android.bluetooth" > +<class name="BluetoothA2dp" + extends="java.lang.Object" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.bluetooth.BluetoothProfile"> +</implements> +<method name="getConnectedDevices" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getConnectionState" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="getDevicesMatchingConnectionStates" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="states" type="int[]"> +</parameter> +</method> +<method name="isA2dpPlaying" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<field name="ACTION_CONNECTION_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_PLAYING_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_NOT_PLAYING" + type="int" + transient="false" + volatile="false" + value="11" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_PLAYING" + type="int" + transient="false" + volatile="false" + value="10" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="BluetoothAdapter" extends="java.lang.Object" abstract="false" @@ -38057,6 +38812,21 @@ <parameter name="address" type="java.lang.String"> </parameter> </method> +<method name="closeProfileProxy" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="profile" type="int"> +</parameter> +<parameter name="proxy" type="android.bluetooth.BluetoothProfile"> +</parameter> +</method> <method name="disable" return="boolean" abstract="false" @@ -38123,6 +38893,23 @@ visibility="public" > </method> +<method name="getProfileProxy" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="listener" type="android.bluetooth.BluetoothProfile.ServiceListener"> +</parameter> +<parameter name="profile" type="int"> +</parameter> +</method> <method name="getRemoteDevice" return="android.bluetooth.BluetoothDevice" abstract="false" @@ -38221,6 +39008,17 @@ visibility="public" > </method> +<field name="ACTION_CONNECTION_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_DISCOVERY_FINISHED" type="java.lang.String" transient="false" @@ -38309,6 +39107,17 @@ visibility="public" > </field> +<field name="EXTRA_CONNECTION_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.adapter.extra.CONNECTION_STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="EXTRA_DISCOVERABLE_DURATION" type="java.lang.String" transient="false" @@ -38331,6 +39140,17 @@ visibility="public" > </field> +<field name="EXTRA_PREVIOUS_CONNECTION_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="EXTRA_PREVIOUS_SCAN_MODE" type="java.lang.String" transient="false" @@ -38408,6 +39228,50 @@ visibility="public" > </field> +<field name="STATE_CONNECTED" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_CONNECTING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTED" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTING" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="STATE_OFF" type="int" transient="false" @@ -39649,6 +40513,306 @@ > </field> </class> +<class name="BluetoothHeadset" + extends="java.lang.Object" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.bluetooth.BluetoothProfile"> +</implements> +<method name="getConnectedDevices" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getConnectionState" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="getDevicesMatchingConnectionStates" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="states" type="int[]"> +</parameter> +</method> +<method name="isAudioConnected" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="startVoiceRecognition" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="stopVoiceRecognition" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<field name="ACTION_AUDIO_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_CONNECTION_STATE_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_AUDIO_CONNECTED" + type="int" + transient="false" + volatile="false" + value="10" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_AUDIO_DISCONNECTED" + type="int" + transient="false" + volatile="false" + value="11" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<interface name="BluetoothProfile" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="getConnectedDevices" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getConnectionState" + return="int" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="device" type="android.bluetooth.BluetoothDevice"> +</parameter> +</method> +<method name="getDevicesMatchingConnectionStates" + return="java.util.Set<android.bluetooth.BluetoothDevice>" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="states" type="int[]"> +</parameter> +</method> +<field name="A2DP" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="EXTRA_PREVIOUS_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.profile.extra.PREVIOUS_STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="EXTRA_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.bluetooth.profile.extra.STATE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="HEADSET" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_CONNECTED" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_CONNECTING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTED" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATE_DISCONNECTING" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</interface> +<interface name="BluetoothProfile.ServiceListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onServiceConnected" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="profile" type="int"> +</parameter> +<parameter name="proxy" type="android.bluetooth.BluetoothProfile"> +</parameter> +</method> +<method name="onServiceDisconnected" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="profile" type="int"> +</parameter> +</method> +</interface> <class name="BluetoothServerSocket" extends="java.lang.Object" abstract="false" @@ -56730,6 +57894,17 @@ visibility="public" > </field> +<field name="FEATURE_AUDIO_LOW_LATENCY" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.hardware.audio.low_latency"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FEATURE_BLUETOOTH" type="java.lang.String" transient="false" @@ -59833,6 +61008,118 @@ > </field> </class> +<class name="ObbInfo" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.os.Parcelable"> +</implements> +<method name="describeContents" + return="int" + 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="dest" type="android.os.Parcel"> +</parameter> +<parameter name="parcelableFlags" 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> +<field name="OBB_OVERLAY" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="flags" + type="int" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="packageName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="version" + type="int" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="ObbScanner" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="getObbInfo" + return="android.content.res.ObbInfo" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="filePath" type="java.lang.String"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +</class> <class name="Resources" extends="java.lang.Object" abstract="false" @@ -66007,7 +67294,7 @@ return="boolean" abstract="false" native="false" - synchronized="true" + synchronized="false" static="false" final="false" deprecated="not deprecated" @@ -66589,19 +67876,6 @@ <exception name="SQLException" type="android.database.SQLException"> </exception> </method> -<method name="setConnectionPoolSize" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="size" type="int"> -</parameter> -</method> <method name="setLocale" return="void" abstract="false" @@ -105876,623 +107150,6 @@ > </field> </class> -<class name="DownloadManager" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<method name="enqueue" - return="long" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="request" type="android.net.DownloadManager.Request"> -</parameter> -</method> -<method name="openDownloadedFile" - return="android.os.ParcelFileDescriptor" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="id" type="long"> -</parameter> -<exception name="FileNotFoundException" type="java.io.FileNotFoundException"> -</exception> -</method> -<method name="query" - return="android.database.Cursor" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="query" type="android.net.DownloadManager.Query"> -</parameter> -</method> -<method name="remove" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="id" type="long"> -</parameter> -</method> -<field name="ACTION_DOWNLOAD_COMPLETE" - type="java.lang.String" - transient="false" - volatile="false" - value=""android.intent.action.DOWNLOAD_COMPLETE"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ACTION_NOTIFICATION_CLICKED" - type="java.lang.String" - transient="false" - volatile="false" - value=""android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ACTION_VIEW_DOWNLOADS" - type="java.lang.String" - transient="false" - volatile="false" - value=""android.intent.action.VIEW_DOWNLOADS"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_BYTES_DOWNLOADED_SO_FAR" - type="java.lang.String" - transient="false" - volatile="false" - value=""bytes_so_far"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_DESCRIPTION" - type="java.lang.String" - transient="false" - volatile="false" - value=""description"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_ERROR_CODE" - type="java.lang.String" - transient="false" - volatile="false" - value=""error_code"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_ID" - type="java.lang.String" - transient="false" - volatile="false" - value=""_id"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_LAST_MODIFIED_TIMESTAMP" - type="java.lang.String" - transient="false" - volatile="false" - value=""last_modified_timestamp"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_LOCAL_URI" - type="java.lang.String" - transient="false" - volatile="false" - value=""local_uri"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_MEDIA_TYPE" - type="java.lang.String" - transient="false" - volatile="false" - value=""media_type"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_STATUS" - type="java.lang.String" - transient="false" - volatile="false" - value=""status"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_TITLE" - type="java.lang.String" - transient="false" - volatile="false" - value=""title"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_TOTAL_SIZE_BYTES" - type="java.lang.String" - transient="false" - volatile="false" - value=""total_size"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="COLUMN_URI" - type="java.lang.String" - transient="false" - volatile="false" - value=""uri"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_CANNOT_RESUME" - type="int" - transient="false" - volatile="false" - value="1008" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_DEVICE_NOT_FOUND" - type="int" - transient="false" - volatile="false" - value="1007" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_FILE_ALREADY_EXISTS" - type="int" - transient="false" - volatile="false" - value="1009" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_FILE_ERROR" - type="int" - transient="false" - volatile="false" - value="1001" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_HTTP_DATA_ERROR" - type="int" - transient="false" - volatile="false" - value="1004" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_INSUFFICIENT_SPACE" - type="int" - transient="false" - volatile="false" - value="1006" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_TOO_MANY_REDIRECTS" - type="int" - transient="false" - volatile="false" - value="1005" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_UNHANDLED_HTTP_CODE" - type="int" - transient="false" - volatile="false" - value="1002" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="ERROR_UNKNOWN" - type="int" - transient="false" - volatile="false" - value="1000" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="EXTRA_DOWNLOAD_ID" - type="java.lang.String" - transient="false" - volatile="false" - value=""extra_download_id"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="STATUS_FAILED" - type="int" - transient="false" - volatile="false" - value="16" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="STATUS_PAUSED" - type="int" - transient="false" - volatile="false" - value="4" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="STATUS_PENDING" - type="int" - transient="false" - volatile="false" - value="1" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="STATUS_RUNNING" - type="int" - transient="false" - volatile="false" - value="2" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="STATUS_SUCCESSFUL" - type="int" - transient="false" - volatile="false" - value="8" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -</class> -<class name="DownloadManager.Query" - extends="java.lang.Object" - abstract="false" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<constructor name="DownloadManager.Query" - type="android.net.DownloadManager.Query" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</constructor> -<method name="setFilterById" - return="android.net.DownloadManager.Query" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="id" type="long"> -</parameter> -</method> -<method name="setFilterByStatus" - return="android.net.DownloadManager.Query" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="flags" type="int"> -</parameter> -</method> -</class> -<class name="DownloadManager.Request" - extends="java.lang.Object" - abstract="false" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<constructor name="DownloadManager.Request" - type="android.net.DownloadManager.Request" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="uri" type="android.net.Uri"> -</parameter> -</constructor> -<method name="addRequestHeader" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="header" type="java.lang.String"> -</parameter> -<parameter name="value" type="java.lang.String"> -</parameter> -</method> -<method name="setAllowedNetworkTypes" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="flags" type="int"> -</parameter> -</method> -<method name="setAllowedOverRoaming" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="allowed" type="boolean"> -</parameter> -</method> -<method name="setDescription" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="description" type="java.lang.CharSequence"> -</parameter> -</method> -<method name="setDestinationInExternalFilesDir" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="context" type="android.content.Context"> -</parameter> -<parameter name="dirType" type="java.lang.String"> -</parameter> -<parameter name="subPath" type="java.lang.String"> -</parameter> -</method> -<method name="setDestinationInExternalPublicDir" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="dirType" type="java.lang.String"> -</parameter> -<parameter name="subPath" type="java.lang.String"> -</parameter> -</method> -<method name="setDestinationUri" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="uri" type="android.net.Uri"> -</parameter> -</method> -<method name="setMimeType" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="mimeType" type="java.lang.String"> -</parameter> -</method> -<method name="setShowRunningNotification" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="show" type="boolean"> -</parameter> -</method> -<method name="setTitle" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="title" type="java.lang.CharSequence"> -</parameter> -</method> -<method name="setVisibleInDownloadsUi" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="isVisible" type="boolean"> -</parameter> -</method> -<field name="NETWORK_MOBILE" - type="int" - transient="false" - volatile="false" - value="1" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="NETWORK_WIFI" - type="int" - transient="false" - volatile="false" - value="2" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -</class> <class name="LocalServerSocket" extends="java.lang.Object" abstract="false" @@ -139639,6 +140296,38 @@ </package> <package name="android.os.storage" > +<class name="OnObbStateChangeListener" + extends="java.lang.Object" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="OnObbStateChangeListener" + type="android.os.storage.OnObbStateChangeListener" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="onObbStateChange" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="java.lang.String"> +</parameter> +<parameter name="state" type="java.lang.String"> +</parameter> +</method> +</class> <class name="StorageEventListener" extends="java.lang.Object" abstract="true" @@ -139780,6 +140469,8 @@ </parameter> <parameter name="key" type="java.lang.String"> </parameter> +<parameter name="listener" type="android.os.storage.OnObbStateChangeListener"> +</parameter> </method> <method name="registerListener" return="void" @@ -139808,6 +140499,8 @@ </parameter> <parameter name="force" type="boolean"> </parameter> +<parameter name="listener" type="android.os.storage.OnObbStateChangeListener"> +</parameter> <exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException"> </exception> </method> @@ -153459,6 +154152,21 @@ <parameter name="volumeName" type="java.lang.String"> </parameter> </method> +<method name="getContentUriForAudioId" + return="android.net.Uri" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="volumeName" type="java.lang.String"> +</parameter> +<parameter name="audioId" type="int"> +</parameter> +</method> <field name="CONTENT_TYPE" type="java.lang.String" transient="false" @@ -155564,6 +156272,17 @@ visibility="public" > </field> +<field name="ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_MANAGE_APPLICATIONS_SETTINGS" type="java.lang.String" transient="false" @@ -240940,7 +241659,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="t" type="T"> +<parameter name="arg0" type="T"> </parameter> </method> </interface> @@ -247861,7 +248580,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="fileName" type="java.lang.String"> +<parameter name="path" type="java.lang.String"> </parameter> <exception name="FileNotFoundException" type="java.io.FileNotFoundException"> </exception> @@ -247984,7 +248703,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="filename" type="java.lang.String"> +<parameter name="path" type="java.lang.String"> </parameter> <exception name="FileNotFoundException" type="java.io.FileNotFoundException"> </exception> @@ -247996,7 +248715,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="filename" type="java.lang.String"> +<parameter name="path" type="java.lang.String"> </parameter> <parameter name="append" type="boolean"> </parameter> @@ -248667,7 +249386,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="b" type="byte[]"> +<parameter name="buffer" type="byte[]"> </parameter> <parameter name="offset" type="int"> </parameter> @@ -283744,9 +284463,9 @@ > <parameter name="dst" type="byte[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="dstOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="byteCount" type="int"> </parameter> </method> <method name="get" @@ -283990,9 +284709,9 @@ > <parameter name="src" type="byte[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="srcOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="byteCount" type="int"> </parameter> </method> <method name="put" @@ -284229,7 +284948,7 @@ </parameter> <parameter name="start" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="byteCount" type="int"> </parameter> </method> </class> @@ -284462,9 +285181,9 @@ > <parameter name="dst" type="char[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="dstOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="charCount" type="int"> </parameter> </method> <method name="get" @@ -284562,9 +285281,9 @@ > <parameter name="src" type="char[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="srcOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="charCount" type="int"> </parameter> </method> <method name="put" @@ -284693,7 +285412,7 @@ </parameter> <parameter name="start" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="charCount" type="int"> </parameter> </method> <method name="wrap" @@ -284854,9 +285573,9 @@ > <parameter name="dst" type="double[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="dstOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="doubleCount" type="int"> </parameter> </method> <method name="get" @@ -284943,9 +285662,9 @@ > <parameter name="src" type="double[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="srcOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="doubleCount" type="int"> </parameter> </method> <method name="put" @@ -285014,7 +285733,7 @@ </parameter> <parameter name="start" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="doubleCount" type="int"> </parameter> </method> </class> @@ -285145,9 +285864,9 @@ > <parameter name="dst" type="float[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="dstOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="floatCount" type="int"> </parameter> </method> <method name="get" @@ -285234,9 +285953,9 @@ > <parameter name="src" type="float[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="srcOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="floatCount" type="int"> </parameter> </method> <method name="put" @@ -285305,7 +286024,7 @@ </parameter> <parameter name="start" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="floatCount" type="int"> </parameter> </method> </class> @@ -285436,9 +286155,9 @@ > <parameter name="dst" type="int[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="dstOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="intCount" type="int"> </parameter> </method> <method name="get" @@ -285525,9 +286244,9 @@ > <parameter name="src" type="int[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="srcOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="intCount" type="int"> </parameter> </method> <method name="put" @@ -285596,7 +286315,7 @@ </parameter> <parameter name="start" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="intCount" type="int"> </parameter> </method> </class> @@ -285744,9 +286463,9 @@ > <parameter name="dst" type="long[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="dstOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="longCount" type="int"> </parameter> </method> <method name="get" @@ -285833,9 +286552,9 @@ > <parameter name="src" type="long[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="srcOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="longCount" type="int"> </parameter> </method> <method name="put" @@ -285904,7 +286623,7 @@ </parameter> <parameter name="start" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="longCount" type="int"> </parameter> </method> </class> @@ -286094,9 +286813,9 @@ > <parameter name="dst" type="short[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="dstOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="shortCount" type="int"> </parameter> </method> <method name="get" @@ -286183,9 +286902,9 @@ > <parameter name="src" type="short[]"> </parameter> -<parameter name="off" type="int"> +<parameter name="srcOffset" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="shortCount" type="int"> </parameter> </method> <method name="put" @@ -286254,7 +286973,7 @@ </parameter> <parameter name="start" type="int"> </parameter> -<parameter name="len" type="int"> +<parameter name="shortCount" type="int"> </parameter> </method> </class> @@ -305844,7 +306563,7 @@ > <parameter name="parameterName" type="java.lang.String"> </parameter> -<parameter name="x" type="java.sql.Blob"> +<parameter name="blob" type="java.sql.Blob"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -306003,7 +306722,7 @@ > <parameter name="parameterName" type="java.lang.String"> </parameter> -<parameter name="x" type="java.sql.Clob"> +<parameter name="clob" type="java.sql.Clob"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -306141,7 +306860,7 @@ > <parameter name="parameterName" type="java.lang.String"> </parameter> -<parameter name="value" type="java.io.Reader"> +<parameter name="reader" type="java.io.Reader"> </parameter> <parameter name="length" type="long"> </parameter> @@ -306177,7 +306896,7 @@ > <parameter name="parameterName" type="java.lang.String"> </parameter> -<parameter name="value" type="java.sql.NClob"> +<parameter name="nclob" type="java.sql.NClob"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -306230,7 +306949,7 @@ > <parameter name="parameterName" type="java.lang.String"> </parameter> -<parameter name="value" type="java.lang.String"> +<parameter name="string" type="java.lang.String"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -306340,7 +307059,7 @@ > <parameter name="parameterName" type="java.lang.String"> </parameter> -<parameter name="x" type="java.sql.RowId"> +<parameter name="rowId" type="java.sql.RowId"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -306357,7 +307076,7 @@ > <parameter name="parameterName" type="java.lang.String"> </parameter> -<parameter name="xmlObject" type="java.sql.SQLXML"> +<parameter name="sqlXml" type="java.sql.SQLXML"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -311493,7 +312212,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="x" type="java.io.InputStream"> +<parameter name="inputStream" type="java.io.InputStream"> </parameter> <parameter name="length" type="long"> </parameter> @@ -311512,7 +312231,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="x" type="java.io.InputStream"> +<parameter name="inputStream" type="java.io.InputStream"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -311565,7 +312284,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="x" type="java.io.InputStream"> +<parameter name="inputStream" type="java.io.InputStream"> </parameter> <parameter name="length" type="long"> </parameter> @@ -311584,7 +312303,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="x" type="java.io.InputStream"> +<parameter name="inputStream" type="java.io.InputStream"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -311917,7 +312636,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="value" type="java.io.Reader"> +<parameter name="reader" type="java.io.Reader"> </parameter> <parameter name="length" type="long"> </parameter> @@ -311936,7 +312655,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="value" type="java.io.Reader"> +<parameter name="reader" type="java.io.Reader"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -312006,7 +312725,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="value" type="java.lang.String"> +<parameter name="theString" type="java.lang.String"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -312133,7 +312852,7 @@ > <parameter name="parameterIndex" type="int"> </parameter> -<parameter name="x" type="java.sql.RowId"> +<parameter name="theRowId" type="java.sql.RowId"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -315127,7 +315846,7 @@ > <parameter name="columnIndex" type="int"> </parameter> -<parameter name="x" type="java.sql.RowId"> +<parameter name="value" type="java.sql.RowId"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -315144,7 +315863,7 @@ > <parameter name="columnLabel" type="java.lang.String"> </parameter> -<parameter name="x" type="java.sql.RowId"> +<parameter name="value" type="java.sql.RowId"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -317476,7 +318195,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="x" type="java.sql.NClob"> +<parameter name="theNClob" type="java.sql.NClob"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -317491,7 +318210,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="x" type="java.lang.String"> +<parameter name="theString" type="java.lang.String"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -317536,7 +318255,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="x" type="java.sql.RowId"> +<parameter name="theRowId" type="java.sql.RowId"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> @@ -317551,7 +318270,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="x" type="java.sql.SQLXML"> +<parameter name="theXml" type="java.sql.SQLXML"> </parameter> <exception name="SQLException" type="java.sql.SQLException"> </exception> diff --git a/cmds/screencap/Android.mk b/cmds/screencap/Android.mk new file mode 100644 index 0000000..1a6e23e --- /dev/null +++ b/cmds/screencap/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + screencap.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libui \ + libsurfaceflinger_client + +LOCAL_MODULE:= screencap + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_EXECUTABLE) diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp new file mode 100644 index 0000000..6ce5b86 --- /dev/null +++ b/cmds/screencap/screencap.cpp @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#include <unistd.h> +#include <fcntl.h> + +#include <utils/Log.h> + +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> + +#include <binder/IMemory.h> +#include <surfaceflinger/ISurfaceComposer.h> + +using namespace android; + +int main(int argc, char** argv) +{ + const String16 name("SurfaceFlinger"); + sp<ISurfaceComposer> composer; + if (getService(name, &composer) != NO_ERROR) + return 0; + + sp<IMemoryHeap> heap; + uint32_t w, h; + PixelFormat f; + status_t err = composer->captureScreen(0, &heap, &w, &h, &f); + if (err != NO_ERROR) + return 0; + + uint8_t* base = (uint8_t*)heap->getBase(); + int fd = dup(STDOUT_FILENO); + write(fd, &w, 4); + write(fd, &h, 4); + write(fd, &f, 4); + write(fd, base, w*h*4); + close(fd); + return 0; +} diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c index a57a72f..d380a27 100644 --- a/cmds/servicemanager/service_manager.c +++ b/cmds/servicemanager/service_manager.c @@ -42,6 +42,7 @@ static struct { { AID_RADIO, "radio.simphonebook" }, /* TODO: remove after phone services are updated: */ { AID_RADIO, "phone" }, + { AID_RADIO, "sip" }, { AID_RADIO, "isms" }, { AID_RADIO, "iphonesubinfo" }, { AID_RADIO, "simphonebook" }, diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index 8b54871..f55b746 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -46,6 +46,7 @@ #include <media/mediametadataretriever.h> #include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MPEG2TSWriter.h> #include <media/stagefright/MPEG4Writer.h> #include <fcntl.h> @@ -366,8 +367,13 @@ status_t DetectSyncSource::read( static void writeSourcesToMP4( Vector<sp<MediaSource> > &sources, bool syncInfoPresent) { +#if 0 sp<MPEG4Writer> writer = new MPEG4Writer(gWriteMP4Filename.string()); +#else + sp<MPEG2TSWriter> writer = + new MPEG2TSWriter(gWriteMP4Filename.string()); +#endif // at most one minute. writer->setMaxFileDuration(60000000ll); diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 73e8d31..b558318 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1297,7 +1297,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } - + + case GET_PROVIDER_MIME_TYPE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Uri uri = Uri.CREATOR.createFromParcel(data); + String type = getProviderMimeType(uri); + reply.writeNoException(); + reply.writeString(type); + return true; + } + case NEW_URI_PERMISSION_OWNER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String name = data.readString(); @@ -2926,6 +2935,20 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public String getProviderMimeType(Uri uri) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + uri.writeToParcel(data, 0); + mRemote.transact(GET_PROVIDER_MIME_TYPE_TRANSACTION, data, reply, 0); + reply.readException(); + String res = reply.readString(); + data.recycle(); + reply.recycle(); + return res; + } + public IBinder newUriPermissionOwner(String name) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2ff88da..b8bbc88 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3377,12 +3377,20 @@ public final class ActivityThread { } } - private final IContentProvider getProvider(Context context, String name) { + private final IContentProvider getExistingProvider(Context context, String name) { synchronized(mProviderMap) { final ProviderClientRecord pr = mProviderMap.get(name); if (pr != null) { return pr.mProvider; } + return null; + } + } + + private final IContentProvider getProvider(Context context, String name) { + IContentProvider existing = getExistingProvider(context, name); + if (existing != null) { + return existing; } IActivityManager.ContentProviderHolder holder = null; @@ -3427,6 +3435,22 @@ public final class ActivityThread { return provider; } + public final IContentProvider acquireExistingProvider(Context c, String name) { + IContentProvider provider = getExistingProvider(c, name); + if(provider == null) + return null; + IBinder jBinder = provider.asBinder(); + synchronized(mProviderMap) { + ProviderRefCount prc = mProviderRefCountMap.get(jBinder); + if(prc == null) { + mProviderRefCountMap.put(jBinder, new ProviderRefCount(1)); + } else { + prc.count++; + } //end else + } //end synchronized + return provider; + } + public final boolean releaseProvider(IContentProvider provider) { if(provider == null) { return false; @@ -3435,7 +3459,7 @@ public final class ActivityThread { synchronized(mProviderMap) { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if(prc == null) { - if(localLOGV) Slog.v(TAG, "releaseProvider::Weird shouldnt be here"); + if(localLOGV) Slog.v(TAG, "releaseProvider::Weird shouldn't be here"); return false; } else { prc.count--; diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java index 61a8fc3..f0477e5 100644 --- a/core/java/android/app/AlertDialog.java +++ b/core/java/android/app/AlertDialog.java @@ -16,12 +16,16 @@ package android.app; +import com.android.internal.app.AlertController; + import android.content.Context; import android.content.DialogInterface; import android.database.Cursor; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.os.Message; +import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; @@ -30,8 +34,6 @@ import android.widget.Button; import android.widget.ListAdapter; import android.widget.ListView; -import com.android.internal.app.AlertController; - /** * A subclass of Dialog that can display one, two or three buttons. If you only want to * display a String in this dialog box, use the setMessage() method. If you @@ -56,7 +58,10 @@ public class AlertDialog extends Dialog implements DialogInterface { private AlertController mAlert; protected AlertDialog(Context context) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert); + this(context, + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog_Alert + : com.android.internal.R.style.Theme_Dialog_Alert); } protected AlertDialog(Context context, int theme) { @@ -65,7 +70,10 @@ public class AlertDialog extends Dialog implements DialogInterface { } protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { - super(context, com.android.internal.R.style.Theme_Dialog_Alert); + super(context, + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog_Alert + : com.android.internal.R.style.Theme_Dialog_Alert); setCancelable(cancelable); setOnCancelListener(cancelListener); mAlert = new AlertController(context, this, getWindow()); @@ -266,12 +274,16 @@ public class AlertDialog extends Dialog implements DialogInterface { public static class Builder { private final AlertController.AlertParams P; private int mTheme; + private Context mWrappedContext; /** * Constructor using a context for this builder and the {@link AlertDialog} it creates. */ public Builder(Context context) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert); + this(context, + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog_Alert + : com.android.internal.R.style.Theme_Dialog_Alert); } /** @@ -284,6 +296,21 @@ public class AlertDialog extends Dialog implements DialogInterface { } /** + * Returns a {@link Context} with the appropriate theme for dialogs created by this Builder. + * Applications should use this Context for obtaining LayoutInflaters for inflating views + * that will be used in the resulting dialogs, as it will cause views to be inflated with + * the correct theme. + * + * @return A Context for built Dialogs. + */ + public Context getContext() { + if (mWrappedContext == null) { + mWrappedContext = new ContextThemeWrapper(P.mContext, mTheme); + } + return mWrappedContext; + } + + /** * Set the title using the given resource id. * * @return This Builder object to allow for chaining of calls to set methods diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 7497136..918ecf1 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -69,13 +69,13 @@ import android.location.LocationManager; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; -import android.net.DownloadManager; import android.net.ThrottleManager; import android.net.IThrottleManager; import android.net.Uri; import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.DropBoxManager; import android.os.Environment; @@ -103,6 +103,8 @@ import android.view.inputmethod.InputMethodManager; import android.accounts.AccountManager; import android.accounts.IAccountManager; import android.app.admin.DevicePolicyManager; +import com.trustedlogic.trustednfc.android.NfcManager; +import com.trustedlogic.trustednfc.android.INfcManager; import com.android.internal.os.IDropBoxManagerService; @@ -173,6 +175,7 @@ class ContextImpl extends Context { private static WifiManager sWifiManager; private static LocationManager sLocationManager; private static CountryDetector sCountryDetector; + private static NfcManager sNfcManager; private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs = new HashMap<String, SharedPreferencesImpl>(); @@ -968,6 +971,8 @@ class ContextImpl extends Context { return getClipboardManager(); } else if (WALLPAPER_SERVICE.equals(name)) { return getWallpaperManager(); + } else if (NFC_SERVICE.equals(name)) { + return getNfcManager(); } else if (DROPBOX_SERVICE.equals(name)) { return getDropBoxManager(); } else if (DEVICE_POLICY_SERVICE.equals(name)) { @@ -1063,8 +1068,13 @@ class ContextImpl extends Context { private NotificationManager getNotificationManager() { synchronized (mSync) { if (mNotificationManager == null) { + final Context outerContext = getOuterContext(); mNotificationManager = new NotificationManager( - new ContextThemeWrapper(getOuterContext(), com.android.internal.R.style.Theme_Dialog), + new ContextThemeWrapper(outerContext, + outerContext.getApplicationInfo().targetSdkVersion >= + Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog + : com.android.internal.R.style.Theme_Dialog), mMainThread.getHandler()); } } @@ -1214,6 +1224,21 @@ class ContextImpl extends Context { return mDownloadManager; } + private NfcManager getNfcManager() + { + synchronized (sSync) { + if (sNfcManager == null) { + IBinder b = ServiceManager.getService(NFC_SERVICE); + if (b == null) { + return null; + } + INfcManager service = INfcManager.Stub.asInterface(b); + sNfcManager = new NfcManager(service, mMainThread.getHandler()); + } + } + return sNfcManager; + } + @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { @@ -1632,22 +1657,23 @@ class ContextImpl extends Context { // ---------------------------------------------------------------------- private static final class ApplicationContentResolver extends ContentResolver { - public ApplicationContentResolver(Context context, - ActivityThread mainThread) - { + public ApplicationContentResolver(Context context, ActivityThread mainThread) { super(context); mMainThread = mainThread; } @Override - protected IContentProvider acquireProvider(Context context, String name) - { + protected IContentProvider acquireProvider(Context context, String name) { return mMainThread.acquireProvider(context, name); } @Override - public boolean releaseProvider(IContentProvider provider) - { + protected IContentProvider acquireExistingProvider(Context context, String name) { + return mMainThread.acquireExistingProvider(context, name); + } + + @Override + public boolean releaseProvider(IContentProvider provider) { return mMainThread.releaseProvider(provider); } diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java index 8ba480d..37f8738 100644 --- a/core/java/android/app/DatePickerDialog.java +++ b/core/java/android/app/DatePickerDialog.java @@ -19,6 +19,7 @@ package android.app; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; +import android.os.Build; import android.os.Bundle; import android.text.TextUtils.TruncateAt; import android.view.LayoutInflater; @@ -82,7 +83,9 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, int year, int monthOfYear, int dayOfMonth) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert, + this(context, context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog_Alert + : com.android.internal.R.style.Theme_Dialog_Alert, callBack, year, monthOfYear, dayOfMonth); } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index a0be0cd..a178c04 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -25,6 +25,7 @@ import android.content.ContextWrapper; import android.content.DialogInterface; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -140,7 +141,10 @@ public class Dialog implements DialogInterface, Window.Callback, */ public Dialog(Context context, int theme) { mContext = new ContextThemeWrapper( - context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme); + context, theme == 0 ? + (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog + : com.android.internal.R.style.Theme_Dialog) : theme); mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; @@ -173,7 +177,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * Retrieve the Context this Dialog is running in. * - * @return Context The Context that was supplied to the constructor. + * @return Context The Context used by the Dialog. */ public final Context getContext() { return mContext; diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java index e8dfac9..8e2389b 100644 --- a/core/java/android/app/DialogFragment.java +++ b/core/java/android/app/DialogFragment.java @@ -199,7 +199,7 @@ public class DialogFragment extends Fragment public void setStyle(int style, int theme) { mStyle = style; if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { - mTheme = android.R.style.Theme_Dialog_NoFrame; + mTheme = com.android.internal.R.style.Theme_Holo_Dialog_NoFrame; } if (theme != 0) { mTheme = theme; diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/app/DownloadManager.java index 3279e8f..69c99cc 100644 --- a/core/java/android/net/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.net; +package android.app; import android.content.ContentResolver; import android.content.ContentUris; @@ -22,6 +22,8 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.CursorWrapper; +import android.net.ConnectivityManager; +import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.BaseColumns; diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 901f117..4d73817 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -317,6 +317,8 @@ public interface IActivityManager extends IInterface { public void crashApplication(int uid, int initialPid, String packageName, String message) throws RemoteException; + + public String getProviderMimeType(Uri uri) throws RemoteException; public IBinder newUriPermissionOwner(String name) throws RemoteException; public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg, @@ -534,8 +536,9 @@ public interface IActivityManager extends IInterface { int SET_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+111; int IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+112; int CRASH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+113; - int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+114; - int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115; - int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116; - int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117; + int GET_PROVIDER_MIME_TYPE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+114; + int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115; + int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116; + int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117; + int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118; } diff --git a/core/java/android/app/ProgressDialog.java b/core/java/android/app/ProgressDialog.java index bdea069..07a5a22 100644 --- a/core/java/android/app/ProgressDialog.java +++ b/core/java/android/app/ProgressDialog.java @@ -18,6 +18,7 @@ package android.app; import android.content.Context; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -73,7 +74,10 @@ public class ProgressDialog extends AlertDialog { private Handler mViewUpdateHandler; public ProgressDialog(Context context) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert); + this(context, + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog_Alert + : com.android.internal.R.style.Theme_Dialog_Alert); } public ProgressDialog(Context context, int theme) { diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index de544fb..fa7f794 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -55,6 +55,18 @@ public class StatusBarManager { public static final int DISABLE_NOTIFICATION_TICKER = 0x00000008; /** + * Flag for {@link #disable} to hide the center system info area. + */ + public static final int DISABLE_SYSTEM_INFO = 0x00000010; + + /** + * Flag for {@link #disable} to hide only the navigation buttons. Don't use this + * unless you're the setup wizard. + */ + public static final int DISABLE_NAVIGATION = 0x00000020; + + + /** * Re-enable all of the status bar features that you've disabled. */ public static final int DISABLE_NONE = 0x00000000; diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index 521d41c..381143c 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -19,6 +19,7 @@ package android.app; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; +import android.os.Build; import android.os.Bundle; import android.text.format.DateFormat; import android.view.LayoutInflater; @@ -76,7 +77,10 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, public TimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert, + this(context, + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB + ? com.android.internal.R.style.Theme_Holo_Dialog_Alert + : com.android.internal.R.style.Theme_Dialog_Alert, callBack, hourOfDay, minute, is24HourView); } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 7e5f858..d308a5c 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -18,88 +18,104 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.server.BluetoothA2dpService; import android.content.Context; -import android.os.ServiceManager; -import android.os.RemoteException; import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.server.BluetoothA2dpService; import android.util.Log; -import java.util.Arrays; import java.util.Collections; -import java.util.Set; +import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; +import java.util.Set; + /** - * Public API for controlling the Bluetooth A2DP Profile Service. - * - * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP - * Service via IPC. + * This class provides the public APIs to control the Bluetooth A2DP + * profile. * - * Creating a BluetoothA2dp object will initiate a binding with the - * BluetoothHeadset service. Users of this object should call close() when they - * are finished, so that this proxy object can unbind from the service. + *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothA2dp proxy object. * - * Currently the BluetoothA2dp service runs in the system server and this - * proxy object will be immediately bound to the service on construction. - * - * Currently this class provides methods to connect to A2DP audio sinks. - * - * @hide + * <p> Android only supports one connected Bluetooth A2dp device at a time. + * Each method is protected with its appropriate permission. */ -public final class BluetoothA2dp { +public final class BluetoothA2dp implements BluetoothProfile { private static final String TAG = "BluetoothA2dp"; private static final boolean DBG = false; - /** int extra for ACTION_SINK_STATE_CHANGED */ - public static final String EXTRA_SINK_STATE = - "android.bluetooth.a2dp.extra.SINK_STATE"; - /** int extra for ACTION_SINK_STATE_CHANGED */ - public static final String EXTRA_PREVIOUS_SINK_STATE = - "android.bluetooth.a2dp.extra.PREVIOUS_SINK_STATE"; + /** + * Intent used to broadcast the change in connection state of the A2DP + * profile. + * + * <p>This intent will have 3 extras: + * {@link #EXTRA_STATE} - The current state of the profile. + * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile + * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. + * + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; - /** Indicates the state of an A2DP audio sink has changed. - * This intent will always contain EXTRA_SINK_STATE, - * EXTRA_PREVIOUS_SINK_STATE and BluetoothDevice.EXTRA_DEVICE - * extras. + /** + * Intent used to broadcast the change in the Playing state of the A2DP + * profile. + * + * <p>This intent will have 3 extras: + * {@link #EXTRA_STATE} - The current state of the profile. + * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile + * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. + * + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_SINK_STATE_CHANGED = - "android.bluetooth.a2dp.action.SINK_STATE_CHANGED"; + public static final String ACTION_PLAYING_STATE_CHANGED = + "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; - public static final int STATE_DISCONNECTED = 0; - public static final int STATE_CONNECTING = 1; - public static final int STATE_CONNECTED = 2; - public static final int STATE_DISCONNECTING = 3; - /** Playing implies connected */ - public static final int STATE_PLAYING = 4; + /** + * A2DP sink device is streaming music. This state can be one of + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of + * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + */ + public static final int STATE_PLAYING = 10; - /** Default priority for a2dp devices that we try to auto-connect - * and allow incoming connections */ - public static final int PRIORITY_AUTO_CONNECT = 1000; - /** Default priority for a2dp devices that should allow incoming - * connections */ - public static final int PRIORITY_ON = 100; - /** Default priority for a2dp devices that should not allow incoming - * connections */ - public static final int PRIORITY_OFF = 0; - /** Default priority when not set or when the device is unpaired */ - public static final int PRIORITY_UNDEFINED = -1; + /** + * A2DP sink device is NOT streaming music. This state can be one of + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of + * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + */ + public static final int STATE_NOT_PLAYING = 11; - private final IBluetoothA2dp mService; - private final Context mContext; + private ServiceListener mServiceListener; + private IBluetoothA2dp mService; + private BluetoothAdapter mAdapter; /** * Create a BluetoothA2dp proxy object for interacting with the local * Bluetooth A2DP service. - * @param c Context + * */ - public BluetoothA2dp(Context c) { - mContext = c; - + /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) { IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE); + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); if (b != null) { mService = IBluetoothA2dp.Stub.asInterface(b); + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this); + } } else { Log.w(TAG, "Bluetooth A2DP service not available!"); @@ -109,167 +125,222 @@ public final class BluetoothA2dp { } } - /** Initiate a connection to an A2DP sink. - * Listen for SINK_STATE_CHANGED_ACTION to find out when the - * connection is completed. - * @param device Remote BT device. - * @return false on immediate error, true otherwise - * @hide + /** + * {@inheritDoc} + * @hide */ - public boolean connectSink(BluetoothDevice device) { - if (DBG) log("connectSink(" + device + ")"); - try { - return mService.connectSink(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; + public boolean connect(BluetoothDevice device) { + if (DBG) log("connect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.connect(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } - /** Initiate disconnect from an A2DP sink. - * Listen for SINK_STATE_CHANGED_ACTION to find out when - * disconnect is completed. - * @param device Remote BT device. - * @return false on immediate error, true otherwise - * @hide + /** + * {@inheritDoc} + * @hide */ - public boolean disconnectSink(BluetoothDevice device) { - if (DBG) log("disconnectSink(" + device + ")"); - try { - return mService.disconnectSink(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } - /** Initiate suspend from an A2DP sink. - * Listen for SINK_STATE_CHANGED_ACTION to find out when - * suspend is completed. - * @param device Remote BT device. - * @return false on immediate error, true otherwise - * @hide + /** + * {@inheritDoc} */ - public boolean suspendSink(BluetoothDevice device) { - try { - return mService.suspendSink(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; + public Set<BluetoothDevice> getConnectedDevices() { + if (DBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { + try { + return toDeviceSet(mService.getConnectedDevices()); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return toDeviceSet(new BluetoothDevice[0]); + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return toDeviceSet(new BluetoothDevice[0]); } - /** Initiate resume from an suspended A2DP sink. - * Listen for SINK_STATE_CHANGED_ACTION to find out when - * resume is completed. - * @param device Remote BT device. - * @return false on immediate error, true otherwise - * @hide + /** + * {@inheritDoc} */ - public boolean resumeSink(BluetoothDevice device) { - try { - return mService.resumeSink(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; + public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (DBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { + try { + return toDeviceSet(mService.getDevicesMatchingConnectionStates(states)); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return toDeviceSet(new BluetoothDevice[0]); + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return toDeviceSet(new BluetoothDevice[0]); } - /** Check if a specified A2DP sink is connected. - * @param device Remote BT device. - * @return True if connected (or playing), false otherwise and on error. - * @hide + /** + * {@inheritDoc} */ - public boolean isSinkConnected(BluetoothDevice device) { - if (DBG) log("isSinkConnected(" + device + ")"); - int state = getSinkState(device); - return state == STATE_CONNECTED || state == STATE_PLAYING; + public int getConnectionState(BluetoothDevice device) { + if (DBG) log("getState(" + device + ")"); + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; } - /** Check if any A2DP sink is connected. - * @return a unmodifiable set of connected A2DP sinks, or null on error. + /** + * {@inheritDoc} * @hide */ - public Set<BluetoothDevice> getConnectedSinks() { - if (DBG) log("getConnectedSinks()"); - try { - return Collections.unmodifiableSet( - new HashSet<BluetoothDevice>(Arrays.asList(mService.getConnectedSinks()))); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return null; + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); + if (mService != null && isEnabled() + && isValidDevice(device)) { + if (priority != BluetoothProfile.PRIORITY_OFF && + priority != BluetoothProfile.PRIORITY_ON) { + return false; + } + try { + return mService.setPriority(device, priority); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } - /** Check if any A2DP sink is in Non Disconnected state - * i.e playing, connected, connecting, disconnecting. - * @return a unmodifiable set of connected A2DP sinks, or null on error. + /** + * {@inheritDoc} * @hide */ - public Set<BluetoothDevice> getNonDisconnectedSinks() { - if (DBG) log("getNonDisconnectedSinks()"); - try { - return Collections.unmodifiableSet( - new HashSet<BluetoothDevice>(Arrays.asList(mService.getNonDisconnectedSinks()))); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return null; + public int getPriority(BluetoothDevice device) { + if (DBG) log("getPriority(" + device + ")"); + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.getPriority(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.PRIORITY_OFF; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.PRIORITY_OFF; } - /** Get the state of an A2DP sink - * @param device Remote BT device. - * @return State code, one of STATE_ - * @hide + /** + * Check if A2DP profile is streaming music. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param device BluetoothDevice device */ - public int getSinkState(BluetoothDevice device) { - if (DBG) log("getSinkState(" + device + ")"); - try { - return mService.getSinkState(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return BluetoothA2dp.STATE_DISCONNECTED; + public boolean isA2dpPlaying(BluetoothDevice device) { + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.isA2dpPlaying(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** - * Set priority of a2dp sink. - * Priority is a non-negative integer. By default paired sinks will have - * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0). - * Sinks with priority greater than zero will accept incoming connections - * (if no sink is currently connected). - * Priority for unpaired sink must be PRIORITY_NONE. - * @param device Paired sink - * @param priority Integer priority, for example PRIORITY_AUTO or - * PRIORITY_NONE - * @return true if priority is set, false on error + * Initiate suspend from an A2DP sink. + * + * <p> This API will return false in scenarios like the A2DP + * device is not in connected state etc. When this API returns, + * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED} + * intent will be broadcasted with the state. Users can get the + * state of the A2DP device from this intent. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param device Remote A2DP sink + * @return false on immediate error, + * true otherwise + * @hide */ - public boolean setSinkPriority(BluetoothDevice device, int priority) { - if (DBG) log("setSinkPriority(" + device + ", " + priority + ")"); - try { - return mService.setSinkPriority(device, priority); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; + public boolean suspendSink(BluetoothDevice device) { + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.suspendSink(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** - * Get priority of a2dp sink. - * @param device Sink - * @return non-negative priority, or negative error code on error. + * Initiate resume from a suspended A2DP sink. + * + * <p> This API will return false in scenarios like the A2DP + * device is not in suspended state etc. When this API returns, + * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED} + * intent will be broadcasted with the state. Users can get the + * state of the A2DP device from this intent. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param device Remote A2DP sink + * @return false on immediate error, + * true otherwise + * @hide */ - public int getSinkPriority(BluetoothDevice device) { - if (DBG) log("getSinkPriority(" + device + ")"); - try { - return mService.getSinkPriority(device); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return PRIORITY_OFF; + public boolean resumeSink(BluetoothDevice device) { + if (mService != null && isEnabled() + && isValidDevice(device)) { + try { + return mService.resumeSink(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } - /** Helper for converting a state to a string. + /** + * Helper for converting a state to a string. + * * For debug use only - strings are not internationalized. * @hide */ @@ -285,12 +356,31 @@ public final class BluetoothA2dp { return "disconnecting"; case STATE_PLAYING: return "playing"; + case STATE_NOT_PLAYING: + return "not playing"; default: return "<unknown state " + state + ">"; } } + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) { + return Collections.unmodifiableSet( + new HashSet<BluetoothDevice>(Arrays.asList(devices))); + } + private static void log(String msg) { - Log.d(TAG, msg); + Log.d(TAG, msg); } } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 33fd395..21a4bd6 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -279,6 +280,61 @@ public final class BluetoothAdapter { */ public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME"; + /** + * Intent used to broadcast the change in connection state of the local + * Bluetooth adapter to a profile of the remote device. When the adapter is + * not connected to any profiles of any remote devices and it attempts a + * connection to a profile this intent will sent. Once connected, this intent + * will not be sent for any more connection attempts to any profiles of any + * remote device. When the adapter disconnects from the last profile its + * connected to of any remote device, this intent will be sent. + * + * <p> This intent is useful for applications that are only concerned about + * whether the local adapter is connected to any profile of any device and + * are not really concerned about which profile. For example, an application + * which displays an icon to display whether Bluetooth is connected or not + * can use this intent. + * + * <p>This intent will have 3 extras: + * {@link #EXTRA_STATE} - The current state. + * {@link #EXTRA_PREVIOUS_STATE}- The previous. + * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. + * + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED"; + + /** + * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED} + * + * This extra represents the current connection state. + */ + public static final String EXTRA_CONNECTION_STATE = + "android.bluetooth.adapter.extra.CONNECTION_STATE"; + + /** + * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED} + * + * This extra represents the previous connection state. + */ + public static final String EXTRA_PREVIOUS_CONNECTION_STATE = + "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE"; + + /** The profile is in disconnected state */ + public static final int STATE_DISCONNECTED = 0; + /** The profile is in connecting state */ + public static final int STATE_CONNECTING = 1; + /** The profile is in connected state */ + public static final int STATE_CONNECTED = 2; + /** The profile is in disconnecting state */ + public static final int STATE_DISCONNECTING = 3; + /** @hide */ public static final String BLUETOOTH_SERVICE = "bluetooth"; @@ -896,6 +952,54 @@ public final class BluetoothAdapter { return null; } + /* + * Get the profile proxy object associated with the profile. + * + * <p>Profile can be one of {@link BluetoothProfile.HEADSET} or + * {@link BluetoothProfile.A2DP}. Clients must implements + * {@link BluetoothProfile.ServiceListener} to get notified of + * the connection status and to get the proxy object. + * + * @param context Context of the application + * @param listener The service Listener for connection callbacks. + * @param profile + * @return true on success, false on error + */ + public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, + int profile) { + if (context == null || listener == null) return false; + + if (profile == BluetoothProfile.HEADSET) { + BluetoothHeadset headset = new BluetoothHeadset(context, listener); + return true; + } else if (profile == BluetoothProfile.A2DP) { + BluetoothA2dp a2dp = new BluetoothA2dp(context, listener); + return true; + } else { + return false; + } + } + + /** + * Close the connection of the profile proxy to the Service. + * + * <p> Clients should call this when they are no longer using + * the proxy obtained from {@link #getProfileProxy}. + * Profile can be one of {@link BluetoothProfile#HEADSET} or + * {@link BluetoothProfile#A2DP} + * + * @param profile + * @param proxy Profile proxy object + */ + public void closeProfileProxy(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.HEADSET) { + BluetoothHeadset headset = (BluetoothHeadset)proxy; + if (headset != null) { + headset.close(); + } + } + } + private Set<BluetoothDevice> toDeviceSet(String[] addresses) { Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length); for (int i = 0; i < addresses.length; i++) { diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java index 1fd7151..e460839 100644 --- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java +++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Message; +import android.bluetooth.BluetoothAdapter; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.util.Log; @@ -28,6 +29,8 @@ import android.util.Log; import com.android.internal.util.HierarchicalState; import com.android.internal.util.HierarchicalStateMachine; +import java.util.Set; + /** * This class is the Profile connection state machine associated with a remote * device. When the device bonds an instance of this class is created. @@ -91,12 +94,11 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine private BluetoothService mService; private BluetoothA2dpService mA2dpService; private BluetoothHeadset mHeadsetService; - private boolean mHeadsetServiceConnected; private BluetoothDevice mDevice; - private int mHeadsetState; - private int mA2dpState; - private int mHidState; + private int mHeadsetState = BluetoothProfile.STATE_DISCONNECTED; + private int mA2dpState = BluetoothProfile.STATE_DISCONNECTED; + private int mHidState = BluetoothProfile.STATE_DISCONNECTED; private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -105,32 +107,29 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (!device.equals(mDevice)) return; - if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); - int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0); - int initiator = intent.getIntExtra( - BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR, - BluetoothHeadset.LOCAL_DISCONNECT); - mHeadsetState = newState; - if (newState == BluetoothHeadset.STATE_DISCONNECTED && - initiator == BluetoothHeadset.REMOTE_DISCONNECT) { - sendMessage(DISCONNECT_HFP_INCOMING); + if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); + mA2dpState = newState; + if (oldState == BluetoothA2dp.STATE_CONNECTED && + newState == BluetoothA2dp.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_A2DP_INCOMING); } - if (newState == BluetoothHeadset.STATE_CONNECTED || - newState == BluetoothHeadset.STATE_DISCONNECTED) { + if (newState == BluetoothProfile.STATE_CONNECTED || + newState == BluetoothProfile.STATE_DISCONNECTED) { sendMessage(TRANSITION_TO_STABLE); } - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); - int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0); - mA2dpState = newState; - if ((oldState == BluetoothA2dp.STATE_CONNECTED || - oldState == BluetoothA2dp.STATE_PLAYING) && - newState == BluetoothA2dp.STATE_DISCONNECTED) { - sendMessage(DISCONNECT_A2DP_INCOMING); + } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); + + mHeadsetState = newState; + if (oldState == BluetoothHeadset.STATE_CONNECTED && + newState == BluetoothHeadset.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_HFP_INCOMING); } - if (newState == BluetoothA2dp.STATE_CONNECTED || - newState == BluetoothA2dp.STATE_DISCONNECTED) { + if (newState == BluetoothProfile.STATE_CONNECTED || + newState == BluetoothProfile.STATE_DISCONNECTED) { sendMessage(TRANSITION_TO_STABLE); } } else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) { @@ -192,31 +191,31 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine IntentFilter filter = new IntentFilter(); // Fine-grained state broadcasts - filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED); mContext.registerReceiver(mBroadcastReceiver, filter); - HeadsetServiceListener l = new HeadsetServiceListener(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEADSET); } - private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener { - public HeadsetServiceListener() { - mHeadsetService = new BluetoothHeadset(mContext, this); - } - public void onServiceConnected() { + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { synchronized(BluetoothDeviceProfileState.this) { - mHeadsetServiceConnected = true; + mHeadsetService = (BluetoothHeadset) proxy; } } - public void onServiceDisconnected() { + public void onServiceDisconnected(int profile) { synchronized(BluetoothDeviceProfileState.this) { - mHeadsetServiceConnected = false; + mHeadsetService = null; } } - } + }; private class BondedDevice extends HierarchicalState { @Override @@ -276,19 +275,25 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine if (isPhoneDocked(mDevice)) { // Don't auto connect to docks. break; - } else if (!mHeadsetServiceConnected) { + } else if (mHeadsetService == null) { deferMessage(message); } else { if (mHeadsetService.getPriority(mDevice) == BluetoothHeadset.PRIORITY_AUTO_CONNECT && - !mHeadsetService.isConnected(mDevice)) { - mHeadsetService.connectHeadset(mDevice); + mHeadsetService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}).size() == 0) { + mHeadsetService.connect(mDevice); } if (mA2dpService != null && - mA2dpService.getSinkPriority(mDevice) == + mA2dpService.getPriority(mDevice) == BluetoothA2dp.PRIORITY_AUTO_CONNECT && - mA2dpService.getConnectedSinks().length == 0) { - mA2dpService.connectSink(mDevice); + mA2dpService.getDevicesMatchingConnectionStates( + new int[] {BluetoothA2dp.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}).length == 0) { + mA2dpService.connect(mDevice); } if (mService.getInputDevicePriority(mDevice) == BluetoothInputDevice.PRIORITY_AUTO_CONNECT) { @@ -805,7 +810,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine synchronized void cancelCommand(int command) { if (command == CONNECT_HFP_OUTGOING ) { // Cancel the outgoing thread. - if (mHeadsetServiceConnected) { + if (mHeadsetService != null) { mHeadsetService.cancelConnectThread(); } // HeadsetService is down. Phone process most likely crashed. @@ -823,12 +828,14 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine log("Processing command:" + command); switch(command) { case CONNECT_HFP_OUTGOING: - if (mHeadsetService != null) { + if (mHeadsetService == null) { + deferHeadsetMessage(command); + } else { return mHeadsetService.connectHeadsetInternal(mDevice); } break; case CONNECT_HFP_INCOMING: - if (!mHeadsetServiceConnected) { + if (mHeadsetService == null) { deferHeadsetMessage(command); } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { return mHeadsetService.acceptIncomingConnect(mDevice); @@ -849,7 +856,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine case CONNECT_HID_INCOMING: return true; case DISCONNECT_HFP_OUTGOING: - if (!mHeadsetServiceConnected) { + if (mHeadsetService == null) { deferHeadsetMessage(command); } else { if (mHeadsetService.getPriority(mDevice) == @@ -867,9 +874,9 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine return true; case DISCONNECT_A2DP_OUTGOING: if (mA2dpService != null) { - if (mA2dpService.getSinkPriority(mDevice) == + if (mA2dpService.getPriority(mDevice) == BluetoothA2dp.PRIORITY_AUTO_CONNECT) { - mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON); + mA2dpService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); } return mA2dpService.disconnectSinkInternal(mDevice); } diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index be21d46..0496b1f 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -18,6 +18,8 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -26,63 +28,66 @@ import android.os.RemoteException; import android.os.IBinder; import android.util.Log; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + /** - * The Android Bluetooth API is not finalized, and *will* change. Use at your - * own risk. - * * Public API for controlling the Bluetooth Headset Service. This includes both - * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will - * attempt a handsfree connection first, and fall back to headset. + * Bluetooth Headset and Handsfree (v1.5) profiles. * - * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset + * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset * Service via IPC. * - * Creating a BluetoothHeadset object will create a binding with the - * BluetoothHeadset service. Users of this object should call close() when they - * are finished with the BluetoothHeadset, so that this proxy object can unbind - * from the service. - * - * This BluetoothHeadset object is not immediately bound to the - * BluetoothHeadset service. Use the ServiceListener interface to obtain a - * notification when it is bound, this is especially important if you wish to - * immediately call methods on BluetoothHeadset after construction. + * <p> Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothHeadset proxy object. Use + * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. * - * Android only supports one connected Bluetooth Headset at a time. - * - * @hide + * <p> Android only supports one connected Bluetooth Headset at a time. + * Each method is protected with its appropriate permission. */ -public final class BluetoothHeadset { - +public final class BluetoothHeadset implements BluetoothProfile { private static final String TAG = "BluetoothHeadset"; private static final boolean DBG = false; + /** + * Intent used to broadcast the change in connection state of the Headset + * profile. + * + * <p>This intent will have 3 extras: + * {@link #EXTRA_STATE} - The current state of the profile. + * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile + * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. + * + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_STATE_CHANGED = - "android.bluetooth.headset.action.STATE_CHANGED"; + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; + /** - * TODO(API release): Consider incorporating as new state in - * HEADSET_STATE_CHANGED + * Intent used to broadcast the change in the Audio Connection state of the + * A2DP profile. + * + * <p>This intent will have 3 extras: + * {@link #EXTRA_STATE} - The current state of the profile. + * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile + * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. + * + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_AUDIO_STATE_CHANGED = - "android.bluetooth.headset.action.AUDIO_STATE_CHANGED"; - public static final String EXTRA_STATE = - "android.bluetooth.headset.extra.STATE"; - public static final String EXTRA_PREVIOUS_STATE = - "android.bluetooth.headset.extra.PREVIOUS_STATE"; - public static final String EXTRA_AUDIO_STATE = - "android.bluetooth.headset.extra.AUDIO_STATE"; - - /** Extra to be used with the Headset State change intent. - * This will be used only when Headset state changes to - * {@link #STATE_DISCONNECTED} from any previous state. - * This extra field is optional and will be used when - * we have deterministic information regarding whether - * the disconnect was initiated by the remote device or - * by the local adapter. - */ - public static final String EXTRA_DISCONNECT_INITIATOR = - "android.bluetooth.headset.extra.DISCONNECT_INITIATOR"; + "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; + /** * Broadcast Action: Indicates a headset has posted a vendor-specific event. @@ -124,100 +129,47 @@ public final class BluetoothHeadset { public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; - - /** - * TODO(API release): Consider incorporating as new state in - * HEADSET_STATE_CHANGED + /* + * Headset state when SCO audio is connected + * This state can be one of + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of + * {@link #ACTION_AUDIO_STATE_CHANGED} intent. */ - private IBluetoothHeadset mService; - private final Context mContext; - private final ServiceListener mServiceListener; - - /** There was an error trying to obtain the state */ - public static final int STATE_ERROR = -1; - /** No headset currently connected */ - public static final int STATE_DISCONNECTED = 0; - /** Connection attempt in progress */ - public static final int STATE_CONNECTING = 1; - /** A headset is currently connected */ - public static final int STATE_CONNECTED = 2; - - /** A SCO audio channel is not established */ - public static final int AUDIO_STATE_DISCONNECTED = 0; - /** A SCO audio channel is established */ - public static final int AUDIO_STATE_CONNECTED = 1; - - public static final int RESULT_FAILURE = 0; - public static final int RESULT_SUCCESS = 1; - /** Connection canceled before completion. */ - public static final int RESULT_CANCELED = 2; - - /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */ - public static final int REMOTE_DISCONNECT = 0; - public static final int LOCAL_DISCONNECT = 1; - - - /** Default priority for headsets that for which we will accept - * inconing connections and auto-connect */ - public static final int PRIORITY_AUTO_CONNECT = 1000; - /** Default priority for headsets that for which we will accept - * inconing connections but not auto-connect */ - public static final int PRIORITY_ON = 100; - /** Default priority for headsets that should not be auto-connected - * and not allow incoming connections. */ - public static final int PRIORITY_OFF = 0; - /** Default priority when not set or when the device is unpaired */ - public static final int PRIORITY_UNDEFINED = -1; + public static final int STATE_AUDIO_CONNECTED = 10; /** - * An interface for notifying BluetoothHeadset IPC clients when they have - * been connected to the BluetoothHeadset service. + * Headset state when SCO audio is NOT connected + * This state can be one of + * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of + * {@link #ACTION_AUDIO_STATE_CHANGED} intent. */ - public interface ServiceListener { - /** - * Called to notify the client when this proxy object has been - * connected to the BluetoothHeadset service. Clients must wait for - * this callback before making IPC calls on the BluetoothHeadset - * service. - */ - public void onServiceConnected(); - - /** - * Called to notify the client that this proxy object has been - * disconnected from the BluetoothHeadset service. Clients must not - * make IPC calls on the BluetoothHeadset service after this callback. - * This callback will currently only occur if the application hosting - * the BluetoothHeadset service, but may be called more often in future. - */ - public void onServiceDisconnected(); - } + public static final int STATE_AUDIO_DISCONNECTED = 11; + + + private Context mContext; + private ServiceListener mServiceListener; + private IBluetoothHeadset mService; + BluetoothAdapter mAdapter; /** * Create a BluetoothHeadset proxy object. */ - public BluetoothHeadset(Context context, ServiceListener l) { + /*package*/ BluetoothHeadset(Context context, ServiceListener l) { mContext = context; mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { Log.e(TAG, "Could not bind to Bluetooth Headset Service"); } } - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - /** * Close the connection to the backing service. * Other public functions of BluetoothHeadset will return default error * results once close() has been called. Multiple invocations of close() * are ok. */ - public synchronized void close() { + /*package*/ synchronized void close() { if (DBG) log("close()"); if (mConnection != null) { mContext.unbindService(mConnection); @@ -226,190 +178,212 @@ public final class BluetoothHeadset { } /** - * Get the current state of the Bluetooth Headset service. - * @return One of the STATE_ return codes, or STATE_ERROR if this proxy - * object is currently not connected to the Headset service. + * {@inheritDoc} + * @hide */ - public int getState(BluetoothDevice device) { - if (DBG) log("getState()"); - if (mService != null) { + public boolean connect(BluetoothDevice device) { + if (DBG) log("connect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { try { - return mService.getState(device); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.connect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } } - return BluetoothHeadset.STATE_ERROR; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** - * Get the BluetoothDevice for the current headset. - * @return current headset, or null if not in connected or connecting - * state, or if this proxy object is not connected to the Headset - * service. + * {@inheritDoc} + * @hide */ - public BluetoothDevice getCurrentHeadset() { - if (DBG) log("getCurrentHeadset()"); - if (mService != null) { + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { try { - return mService.getCurrentHeadset(); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } } - return null; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** - * Request to initiate a connection to a headset. - * This call does not block. Fails if a headset is already connecting - * or connected. - * Initiates auto-connection if device is null. Tries to connect to all - * devices with priority greater than PRIORITY_AUTO in descending order. - * @param device device to connect to, or null to auto-connect last connected - * headset - * @return false if there was a problem initiating the connection - * procedure, and no further HEADSET_STATE_CHANGED intents - * will be expected. + * {@inheritDoc} */ - public boolean connectHeadset(BluetoothDevice device) { - if (DBG) log("connectHeadset(" + device + ")"); - if (mService != null) { + public Set<BluetoothDevice> getConnectedDevices() { + if (DBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { try { - if (mService.connectHeadset(device)) { - return true; - } - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return toDeviceSet(mService.getConnectedDevices()); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return toDeviceSet(new BluetoothDevice[0]); + } } - return false; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return toDeviceSet(new BluetoothDevice[0]); } /** - * Returns true if the specified headset is connected (does not include - * connecting). Returns false if not connected, or if this proxy object - * if not currently connected to the headset service. + * {@inheritDoc} */ - public boolean isConnected(BluetoothDevice device) { - if (DBG) log("isConnected(" + device + ")"); - if (mService != null) { + public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (DBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { try { - return mService.isConnected(device); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return toDeviceSet(mService.getDevicesMatchingConnectionStates(states)); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return toDeviceSet(new BluetoothDevice[0]); + } } - return false; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return toDeviceSet(new BluetoothDevice[0]); } /** - * Disconnects the current headset. Currently this call blocks, it may soon - * be made asynchornous. Returns false if this proxy object is - * not currently connected to the Headset service. + * {@inheritDoc} */ - public boolean disconnectHeadset(BluetoothDevice device) { - if (DBG) log("disconnectHeadset()"); - if (mService != null) { + public int getConnectionState(BluetoothDevice device) { + if (DBG) log("getConnectionState(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { try { - mService.disconnectHeadset(device); - return true; - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } } - return false; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; } /** - * Start BT Voice Recognition mode, and set up Bluetooth audio path. - * Returns false if there is no headset connected, or if the - * connected headset does not support voice recognition, or on - * error. + * {@inheritDoc} + * @hide */ - public boolean startVoiceRecognition() { - if (DBG) log("startVoiceRecognition()"); - if (mService != null) { + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + if (priority != BluetoothProfile.PRIORITY_OFF && + priority != BluetoothProfile.PRIORITY_ON) { + return false; + } try { - return mService.startVoiceRecognition(); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.setPriority(device, priority); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); return false; } /** - * Stop BT Voice Recognition mode, and shut down Bluetooth audio path. - * Returns false if there is no headset connected, or the connected - * headset is not in voice recognition mode, or on error. + * {@inheritDoc} + * @hide */ - public boolean stopVoiceRecognition() { - if (DBG) log("stopVoiceRecognition()"); - if (mService != null) { + public int getPriority(BluetoothDevice device) { + if (DBG) log("getPriority(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { try { - return mService.stopVoiceRecognition(); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.getPriority(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return PRIORITY_OFF; + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return PRIORITY_OFF; + } + + /** + * Start Bluetooth voice recognition. This methods sends the voice + * recognition AT command to the headset and establishes the + * audio connection. + * + * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. + * {@link #EXTRA_STATE} will be set to {@link #STATE_AUDIO_CONNECTED} + * when the audio connection is established. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param device Bluetooth headset + * @return false if there is no headset connected of if the + * connected headset doesn't support voice recognition + * or on error, true otherwise + */ + public boolean startVoiceRecognition(BluetoothDevice device) { + if (DBG) log("startVoiceRecognition()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.startVoiceRecognition(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); return false; } /** - * Set priority of headset. - * Priority is a non-negative integer. By default paired headsets will have - * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0). - * Headsets with priority greater than zero will be auto-connected, and - * incoming connections will be accepted (if no other headset is - * connected). - * Auto-connection occurs at the following events: boot, incoming phone - * call, outgoing phone call. - * Headsets with priority equal to zero, or that are unpaired, are not - * auto-connected. - * Incoming connections are ignored regardless of priority if there is - * already a headset connected. - * @param device paired headset - * @param priority Integer priority, for example PRIORITY_AUTO or - * PRIORITY_NONE - * @return true if successful, false if there was some error + * Stop Bluetooth Voice Recognition mode, and shut down the + * Bluetooth audio path. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param device Bluetooth headset + * @return false if there is no headset connected + * or on error, true otherwise */ - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null) { + public boolean stopVoiceRecognition(BluetoothDevice device) { + if (DBG) log("stopVoiceRecognition()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { try { - return mService.setPriority(device, priority); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.stopVoiceRecognition(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); return false; } /** - * Get priority of headset. - * @param device headset - * @return non-negative priority, or negative error code on error + * Check if Bluetooth SCO audio is connected. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param device Bluetooth headset + * @return true if SCO is connected, + * false otherwise or on error */ - public int getPriority(BluetoothDevice device) { - if (DBG) log("getPriority(" + device + ")"); - if (mService != null) { + public boolean isAudioConnected(BluetoothDevice device) { + if (DBG) log("isAudioConnected()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { try { - return mService.getPriority(device); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.isAudioConnected(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } } - return -1; + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; } /** @@ -420,24 +394,29 @@ public final class BluetoothHeadset { * boot. This is a good indicator for spammy headset/handsfree units that * can keep the device awake by polling for cellular status updates. As a * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms + * + * @param device the bluetooth headset. * @return monotonically increasing battery usage hint, or a negative error * code on error * @hide */ - public int getBatteryUsageHint() { + public int getBatteryUsageHint(BluetoothDevice device) { if (DBG) log("getBatteryUsageHint()"); - if (mService != null) { + if (mService != null && isEnabled() && + isValidDevice(device)) { try { - return mService.getBatteryUsageHint(); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.getBatteryUsageHint(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); return -1; } + /** * Indicates if current platform supports voice dialing over bluetooth SCO. + * * @return true if voice dialing over bluetooth is supported, false otherwise. * @hide */ @@ -448,11 +427,13 @@ public final class BluetoothHeadset { /** * Cancel the outgoing connection. + * Note: This is an internal function and shouldn't be exposed + * * @hide */ public boolean cancelConnectThread() { if (DBG) log("cancelConnectThread"); - if (mService != null) { + if (mService != null && isEnabled()) { try { return mService.cancelConnectThread(); } catch (RemoteException e) {Log.e(TAG, e.toString());} @@ -465,11 +446,13 @@ public final class BluetoothHeadset { /** * Accept the incoming connection. + * Note: This is an internal function and shouldn't be exposed + * * @hide */ public boolean acceptIncomingConnect(BluetoothDevice device) { if (DBG) log("acceptIncomingConnect"); - if (mService != null) { + if (mService != null && isEnabled()) { try { return mService.acceptIncomingConnect(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} @@ -481,12 +464,14 @@ public final class BluetoothHeadset { } /** - * Create the connect thread the incoming connection. + * Create the connect thread for the incoming connection. + * Note: This is an internal function and shouldn't be exposed + * * @hide */ public boolean createIncomingConnect(BluetoothDevice device) { if (DBG) log("createIncomingConnect"); - if (mService != null) { + if (mService != null && isEnabled()) { try { return mService.createIncomingConnect(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} @@ -500,11 +485,12 @@ public final class BluetoothHeadset { /** * Connect to a Bluetooth Headset. * Note: This is an internal function and shouldn't be exposed + * * @hide */ public boolean connectHeadsetInternal(BluetoothDevice device) { if (DBG) log("connectHeadsetInternal"); - if (mService != null) { + if (mService != null && isEnabled()) { try { return mService.connectHeadsetInternal(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} @@ -518,11 +504,12 @@ public final class BluetoothHeadset { /** * Disconnect a Bluetooth Headset. * Note: This is an internal function and shouldn't be exposed + * * @hide */ public boolean disconnectHeadsetInternal(BluetoothDevice device) { if (DBG) log("disconnectHeadsetInternal"); - if (mService != null) { + if (mService != null && isEnabled()) { try { return mService.disconnectHeadsetInternal(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} @@ -532,23 +519,61 @@ public final class BluetoothHeadset { } return false; } + + /** + * Set the audio state of the Headset. + * Note: This is an internal function and shouldn't be exposed + * + * @hide + */ + public boolean setAudioState(BluetoothDevice device, int state) { + if (DBG) log("setAudioState"); + if (mService != null && isEnabled()) { + try { + return mService.setAudioState(device, state); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothHeadset.Stub.asInterface(service); + if (mServiceListener != null) { - mServiceListener.onServiceConnected(); + mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this); } } public void onServiceDisconnected(ComponentName className) { if (DBG) Log.d(TAG, "Proxy object disconnected"); mService = null; if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(); + mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); } } }; + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) { + return Collections.unmodifiableSet( + new HashSet<BluetoothDevice>(Arrays.asList(devices))); + } + private static void log(String msg) { Log.d(TAG, msg); } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java new file mode 100644 index 0000000..3b4c84c --- /dev/null +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -0,0 +1,239 @@ +/* + * 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 android.bluetooth; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; + +import java.util.Set; + +/** + * Public APIs for the Bluetooth Profiles. + * + * <p> Clients should call {@link BluetoothAdapter#getProfileProxy}, + * to get the Profile Proxy. Each public profile implements this + * interface. + */ +public interface BluetoothProfile { + + /** + * Extra for the connection state intents of the individual profiles. + * + * This extra represents the current connection state of the profile of the + * Bluetooth device. + */ + public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE"; + + /** + * Extra for the connection state intents of the individual profiles. + * + * This extra represents the previous connection state of the profile of the + * Bluetooth device. + */ + public static final String EXTRA_PREVIOUS_STATE = + "android.bluetooth.profile.extra.PREVIOUS_STATE"; + + /** The profile is in disconnected state */ + public static final int STATE_DISCONNECTED = 0; + /** The profile is in connecting state */ + public static final int STATE_CONNECTING = 1; + /** The profile is in connected state */ + public static final int STATE_CONNECTED = 2; + /** The profile is in disconnecting state */ + public static final int STATE_DISCONNECTING = 3; + + /** + * Headset and Handsfree profile + */ + public static final int HEADSET = 1; + /** + * A2DP profile. + */ + public static final int A2DP = 2; + + /** + * Default priority for devices that we try to auto-connect to and + * and allow incoming connections for the profile + * @hide + **/ + public static final int PRIORITY_AUTO_CONNECT = 1000; + + /** + * Default priority for devices that allow incoming + * and outgoing connections for the profile + * @hide + **/ + public static final int PRIORITY_ON = 100; + + /** + * Default priority for devices that does not allow incoming + * connections and outgoing connections for the profile. + * @hide + **/ + public static final int PRIORITY_OFF = 0; + + /** + * Default priority when not set or when the device is unpaired + * @hide + * */ + public static final int PRIORITY_UNDEFINED = -1; + + /** + * Initiate connection to a profile of the remote bluetooth device. + * + * <p> Currently, the system supports only 1 connection to the + * A2DP and Headset/Handsfree profile. The API will automatically + * disconnect connected devices before connecting. + * + * <p> This API returns false in scenarios like the profile on the + * device is already connected or Bluetooth is not turned on. + * When this API returns true, it is guaranteed that + * connection state intent for the profile will be broadcasted with + * the state. Users can get the connection state of the profile + * from this intent. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean connect(BluetoothDevice device); + + /** + * Initiate disconnection from a profile + * + * <p> This API will return false in scenarios like the profile on the + * Bluetooth device is not in connected state etc. When this API returns, + * true, it is guaranteed that the connection state change + * intent will be broadcasted with the state. Users can get the + * disconnection state of the profile from this intent. + * + * <p> If the disconnection is initiated by a remote device, the state + * will transition from {@link #STATE_CONNECTED} to + * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the + * host (local) device the state will transition from + * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to + * state {@link #STATE_DISCONNECTED}. The transition to + * {@link #STATE_DISCONNECTING} can be used to distinguish between the + * two scenarios. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean disconnect(BluetoothDevice device); + + /** + * Get connected devices for this specific profile. + * + * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return An unmodifiable set of devices. The set will be empty on error. + */ + public Set<BluetoothDevice> getConnectedDevices(); + + /** + * Get a set of devices that match any of the given connection + * states. + * + * <p> If none of devices match any of the given states, + * an empty set will be returned. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param states Array of states. States can be one of + * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, + * @return An unmodifiable set of devices. The set will be empty on error. + */ + public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states); + + /** + * Get the current connection state of the profile + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param device Remote bluetooth device. + * @return State of the profile connection. One of + * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} + */ + public int getConnectionState(BluetoothDevice device); + + /** + * Set priority of the profile + * + * <p> The device should already be paired. + * Priority can be one of {@link #PRIORITY_ON} or + * {@link #PRIORITY_OFF}, + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param device Paired bluetooth device + * @param priority + * @return true if priority is set, false on error + * @hide + */ + public boolean setPriority(BluetoothDevice device, int priority); + + /** + * Get the priority of the profile. + * + * <p> The priority can be any of: + * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, + * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param device Bluetooth device + * @return priority of the device + * @hide + */ + public int getPriority(BluetoothDevice device); + + /** + * An interface for notifying BluetoothProfile IPC clients when they have + * been connected or disconnected to the service. + */ + public interface ServiceListener { + /** + * Called to notify the client when the proxy object has been + * connected to the service. + * @param profile - One of {@link #HEADSET} or + * {@link #A2DP} + * @param proxy - One of {@link BluetoothHeadset} or + * {@link BluetoothA2dp} + */ + public void onServiceConnected(int profile, BluetoothProfile proxy); + + /** + * Called to notify the client that this proxy object has been + * disconnected from the service. + * @param profile - One of {@link #HEADSET} or + * {@link #A2DP} + */ + public void onServiceDisconnected(int profile); + } +} diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java index ad70d0d..7f42baf 100644 --- a/core/java/android/bluetooth/BluetoothProfileState.java +++ b/core/java/android/bluetooth/BluetoothProfileState.java @@ -59,16 +59,16 @@ public class BluetoothProfileState extends HierarchicalStateMachine { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); - if (mProfile == HFP && (newState == BluetoothHeadset.STATE_CONNECTED || - newState == BluetoothHeadset.STATE_DISCONNECTED)) { + if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + if (mProfile == HFP && (newState == BluetoothProfile.STATE_CONNECTED || + newState == BluetoothProfile.STATE_DISCONNECTED)) { sendMessage(TRANSITION_TO_STABLE); } - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); - if (mProfile == A2DP && (newState == BluetoothA2dp.STATE_CONNECTED || - newState == BluetoothA2dp.STATE_DISCONNECTED)) { + } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + if (mProfile == A2DP && (newState == BluetoothProfile.STATE_CONNECTED || + newState == BluetoothProfile.STATE_DISCONNECTED)) { sendMessage(TRANSITION_TO_STABLE); } } else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) { @@ -89,8 +89,8 @@ public class BluetoothProfileState extends HierarchicalStateMachine { setInitialState(mStableState); IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); + filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED); context.registerReceiver(mBroadcastReceiver, filter); } diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 40f1058..c5044c2 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -19,21 +19,25 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; /** - * System private API for Bluetooth A2DP service + * APIs for Bluetooth A2DP service * - * {@hide} + * @hide */ interface IBluetoothA2dp { - boolean connectSink(in BluetoothDevice device); - boolean disconnectSink(in BluetoothDevice device); + // Public API + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + // change to Set<> once AIDL supports + BluetoothDevice[] getConnectedDevices(); + BluetoothDevice[] getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); + boolean isA2dpPlaying(in BluetoothDevice device); + + // Internal APIs boolean suspendSink(in BluetoothDevice device); boolean resumeSink(in BluetoothDevice device); - BluetoothDevice[] getConnectedSinks(); // change to Set<> once AIDL supports - BluetoothDevice[] getNonDisconnectedSinks(); // change to Set<> once AIDL supports - int getSinkState(in BluetoothDevice device); - boolean setSinkPriority(in BluetoothDevice device, int priority); - int getSinkPriority(in BluetoothDevice device); - boolean connectSinkInternal(in BluetoothDevice device); boolean disconnectSinkInternal(in BluetoothDevice device); } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index d96f0ca..8bcf103 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -19,25 +19,32 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; /** - * System private API for Bluetooth Headset service + * API for Bluetooth Headset service * * {@hide} */ interface IBluetoothHeadset { - int getState(in BluetoothDevice device); - BluetoothDevice getCurrentHeadset(); - boolean connectHeadset(in BluetoothDevice device); - void disconnectHeadset(in BluetoothDevice device); - boolean isConnected(in BluetoothDevice device); - boolean startVoiceRecognition(); - boolean stopVoiceRecognition(); + // Public API + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + // Change to Set<> when AIDL supports + BluetoothDevice[] getConnectedDevices(); + BluetoothDevice[] getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); boolean setPriority(in BluetoothDevice device, int priority); int getPriority(in BluetoothDevice device); - int getBatteryUsageHint(); + boolean startVoiceRecognition(in BluetoothDevice device); + boolean stopVoiceRecognition(in BluetoothDevice device); + boolean isAudioConnected(in BluetoothDevice device); + // APIs that can be made public in future + int getBatteryUsageHint(in BluetoothDevice device); + + // Internal functions, not be made public boolean createIncomingConnect(in BluetoothDevice device); boolean acceptIncomingConnect(in BluetoothDevice device); boolean cancelConnectThread(); boolean connectHeadsetInternal(in BluetoothDevice device); boolean disconnectHeadsetInternal(in BluetoothDevice device); + boolean setAudioState(in BluetoothDevice device, int state); } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index d3c1b4e..6bb32c1 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -561,6 +561,12 @@ public abstract class ContentProvider implements ComponentCallbacks { * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. * + * <p>Note that there are no permissions needed for an application to + * access this information; if your content provider requires read and/or + * write permissions, or is not exported, all applications can still call + * this method regardless of their access permissions. This allows them + * to retrieve the MIME type for a URI when dispatching intents. + * * @param uri the URI to query. * @return a MIME type string, or null if there is no type. */ diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 22feb9a..3289120 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -17,6 +17,7 @@ package android.content; import android.accounts.Account; +import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.AppGlobals; import android.content.pm.PackageManager.NameNotFoundException; @@ -176,6 +177,12 @@ public abstract class ContentResolver { /** @hide */ protected abstract IContentProvider acquireProvider(Context c, String name); + /** Providing a default implementation of this, to avoid having to change + * a lot of other things, but implementations of ContentResolver should + * implement it. @hide */ + protected IContentProvider acquireExistingProvider(Context c, String name) { + return acquireProvider(c, name); + } /** @hide */ public abstract boolean releaseProvider(IContentProvider icp); @@ -187,18 +194,28 @@ public abstract class ContentResolver { * @return A MIME type for the content, or null if the URL is invalid or the type is unknown */ public final String getType(Uri url) { - IContentProvider provider = acquireProvider(url); - if (provider == null) { + IContentProvider provider = acquireExistingProvider(url); + if (provider != null) { + try { + return provider.getType(url); + } catch (RemoteException e) { + return null; + } catch (java.lang.Exception e) { + return null; + } finally { + releaseProvider(provider); + } + } + + if (!SCHEME_CONTENT.equals(url.getScheme())) { return null; } + try { - return provider.getType(url); + String type = ActivityManagerNative.getDefault().getProviderMimeType(url); + return type; } catch (RemoteException e) { return null; - } catch (java.lang.Exception e) { - return null; - } finally { - releaseProvider(provider); } } @@ -224,15 +241,14 @@ public abstract class ContentResolver { if (provider == null) { return null; } + try { return provider.getStreamTypes(url, mimeTypeFilter); } catch (RemoteException e) { return null; - } catch (java.lang.Exception e) { - return null; } finally { - releaseProvider(provider); - } + releaseProvider(provider); + } } /** @@ -821,14 +837,13 @@ public abstract class ContentResolver { } /** - * Returns the content provider for the given content URI.. + * Returns the content provider for the given content URI. * * @param uri The URI to a content provider * @return The ContentProvider for the given URI, or null if no content provider is found. * @hide */ - public final IContentProvider acquireProvider(Uri uri) - { + public final IContentProvider acquireProvider(Uri uri) { if (!SCHEME_CONTENT.equals(uri.getScheme())) { return null; } @@ -840,6 +855,25 @@ public abstract class ContentResolver { } /** + * Returns the content provider for the given content URI if the process + * already has a reference on it. + * + * @param uri The URI to a content provider + * @return The ContentProvider for the given URI, or null if no content provider is found. + * @hide + */ + public final IContentProvider acquireExistingProvider(Uri uri) { + if (!SCHEME_CONTENT.equals(uri.getScheme())) { + return null; + } + String auth = uri.getAuthority(); + if (auth != null) { + return acquireExistingProvider(mContext, uri.getAuthority()); + } + return null; + } + + /** * @hide */ public final IContentProvider acquireProvider(String name) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3d29379..9c8d698 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1235,7 +1235,7 @@ public abstract class Context { * <dt> {@link #UI_MODE_SERVICE} ("uimode") * <dd> An {@link android.app.UiModeManager} for controlling UI modes. * <dt> {@link #DOWNLOAD_SERVICE} ("download") - * <dd> A {@link android.net.DownloadManager} for requesting HTTP downloads + * <dd> A {@link android.app.DownloadManager} for requesting HTTP downloads * </dl> * * <p>Note: System services obtained via this API may be closely associated with @@ -1284,7 +1284,7 @@ public abstract class Context { * @see #UI_MODE_SERVICE * @see android.app.UiModeManager * @see #DOWNLOAD_SERVICE - * @see android.net.DownloadManager + * @see android.app.DownloadManager */ public abstract Object getSystemService(String name); @@ -1576,7 +1576,7 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@link android.net.DownloadManager} for requesting HTTP downloads. + * {@link android.app.DownloadManager} for requesting HTTP downloads. * * @see #getSystemService */ @@ -1592,6 +1592,16 @@ public abstract class Context { public static final String SIP_SERVICE = "sip"; /** + * Use with {@link #getSystemService} to retrieve an + * {@link com.trustedlogic.trustednfc.android.INfcManager.INfcManager} for + * accessing NFC methods. + * + * @see #getSystemService + * @hide + */ + public static final String NFC_SERVICE = "nfc"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 1a3bcc4..cb6b708 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -636,6 +636,15 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's audio pipeline is low-latency, + * more suitable for audio applications sensitive to delays or lag in + * sound input or output. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device is capable of communicating with * other devices via Bluetooth. */ diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 92a8a99..8ef639b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -36,6 +36,7 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -332,7 +333,7 @@ public class PackageParser { try { // We must read the stream for the JarEntry to retrieve // its certificates. - InputStream is = jarFile.getInputStream(je); + InputStream is = new BufferedInputStream(jarFile.getInputStream(je)); while (is.read(readBuffer, 0, readBuffer.length) != -1) { // not using } diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java index 838c5ff..7b962e5 100644 --- a/core/java/android/content/res/ObbInfo.java +++ b/core/java/android/content/res/ObbInfo.java @@ -20,9 +20,9 @@ import android.os.Parcel; import android.os.Parcelable; /** - * Basic information about a Opaque Binary Blob (OBB) that reflects - * the info from the footer on the OBB file. - * @hide + * Basic information about a Opaque Binary Blob (OBB) that reflects the info + * from the footer on the OBB file. This information may be manipulated by a + * developer with the <code>obbtool</code> program in the Android SDK. */ public class ObbInfo implements Parcelable { /** Flag noting that this OBB is an overlay patch for a base OBB. */ @@ -43,7 +43,8 @@ public class ObbInfo implements Parcelable { */ public int flags; - public ObbInfo() { + // Only allow things in this package to instantiate. + /* package */ ObbInfo() { } public String toString() { diff --git a/core/java/android/content/res/ObbScanner.java b/core/java/android/content/res/ObbScanner.java index eb383c3..a3f141e 100644 --- a/core/java/android/content/res/ObbScanner.java +++ b/core/java/android/content/res/ObbScanner.java @@ -16,25 +16,43 @@ package android.content.res; +import java.io.File; +import java.io.IOException; + /** - * Class to scan Opaque Binary Blob (OBB) files. - * @hide + * Class to scan Opaque Binary Blob (OBB) files. Use this to get information + * about an OBB file for use in a program via {@link ObbInfo}. */ public class ObbScanner { // Don't allow others to instantiate this class private ObbScanner() {} - public static ObbInfo getObbInfo(String filePath) { + /** + * Scan a file for OBB information. + * + * @param filePath path to the OBB file to be scanned. + * @return ObbInfo object information corresponding to the file path + * @throws IllegalArgumentException if the OBB file couldn't be found + * @throws IOException if the OBB file couldn't be read + */ + public static ObbInfo getObbInfo(String filePath) throws IOException { if (filePath == null) { - return null; + throw new IllegalArgumentException("file path cannot be null"); } - ObbInfo obbInfo = new ObbInfo(); - if (!getObbInfo_native(filePath, obbInfo)) { - throw new IllegalArgumentException("Could not read OBB file: " + filePath); + final File obbFile = new File(filePath); + if (!obbFile.exists()) { + throw new IllegalArgumentException("OBB file does nto exist: " + filePath); } + + final String canonicalFilePath = obbFile.getCanonicalPath(); + + ObbInfo obbInfo = new ObbInfo(); + getObbInfo_native(canonicalFilePath, obbInfo); + return obbInfo; } - private native static boolean getObbInfo_native(String filePath, ObbInfo obbInfo); + private native static void getObbInfo_native(String filePath, ObbInfo obbInfo) + throws IOException; } diff --git a/core/java/android/database/sqlite/DatabaseConnectionPool.java b/core/java/android/database/sqlite/DatabaseConnectionPool.java index 4f5c4e6..39a9d23 100644 --- a/core/java/android/database/sqlite/DatabaseConnectionPool.java +++ b/core/java/android/database/sqlite/DatabaseConnectionPool.java @@ -58,16 +58,14 @@ import java.util.Random; /** * close all database connections in the pool - even if they are in use! */ - /* package */ void close() { - synchronized(mParentDbObj) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Closing the connection pool on " + mParentDbObj.getPath() + toString()); - } - for (int i = mPool.size() - 1; i >= 0; i--) { - mPool.get(i).mDb.close(); - } - mPool.clear(); + /* package */ synchronized void close() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Closing the connection pool on " + mParentDbObj.getPath() + toString()); + } + for (int i = mPool.size() - 1; i >= 0; i--) { + mPool.get(i).mDb.close(); } + mPool.clear(); } /** @@ -77,76 +75,73 @@ import java.util.Random; * the compiled statement for this sql. * @return the Database connection that the caller can use */ - /* package */ SQLiteDatabase get(String sql) { + /* package */ synchronized SQLiteDatabase get(String sql) { SQLiteDatabase db = null; PoolObj poolObj = null; - synchronized(mParentDbObj) { - int poolSize = mPool.size(); - if (Log.isLoggable(TAG, Log.DEBUG)) { - assert sql != null; - doAsserts(); - } - if (getFreePoolSize() == 0) { - // no free ( = available) connections - if (mMaxPoolSize == poolSize) { - // maxed out. can't open any more connections. - // let the caller wait on one of the pooled connections - // preferably a connection caching the pre-compiled statement of the given SQL - if (mMaxPoolSize == 1) { - poolObj = mPool.get(0); - } else { - for (int i = 0; i < mMaxPoolSize; i++) { - if (mPool.get(i).mDb.isSqlInStatementCache(sql)) { - poolObj = mPool.get(i); - break; - } + int poolSize = mPool.size(); + if (Log.isLoggable(TAG, Log.DEBUG)) { + assert sql != null; + doAsserts(); + } + if (getFreePoolSize() == 0) { + // no free ( = available) connections + if (mMaxPoolSize == poolSize) { + // maxed out. can't open any more connections. + // let the caller wait on one of the pooled connections + // preferably a connection caching the pre-compiled statement of the given SQL + if (mMaxPoolSize == 1) { + poolObj = mPool.get(0); + } else { + for (int i = 0; i < mMaxPoolSize; i++) { + if (mPool.get(i).mDb.isInStatementCache(sql)) { + poolObj = mPool.get(i); + break; } - if (poolObj == null) { - // there are no database connections with the given SQL pre-compiled. - // ok to return any of the connections. - if (rand == null) { - rand = new Random(SystemClock.elapsedRealtime()); - } - poolObj = mPool.get(rand.nextInt(mMaxPoolSize)); + } + if (poolObj == null) { + // there are no database connections with the given SQL pre-compiled. + // ok to return any of the connections. + if (rand == null) { + rand = new Random(SystemClock.elapsedRealtime()); } + poolObj = mPool.get(rand.nextInt(mMaxPoolSize)); } - db = poolObj.mDb; - } else { - // create a new connection and add it to the pool, since we haven't reached - // max pool size allowed - db = mParentDbObj.createPoolConnection((short)(poolSize + 1)); - poolObj = new PoolObj(db); - mPool.add(poolSize, poolObj); } + db = poolObj.mDb; } else { - // there are free connections available. pick one - // preferably a connection caching the pre-compiled statement of the given SQL + // create a new connection and add it to the pool, since we haven't reached + // max pool size allowed + db = mParentDbObj.createPoolConnection((short)(poolSize + 1)); + poolObj = new PoolObj(db); + mPool.add(poolSize, poolObj); + } + } else { + // there are free connections available. pick one + // preferably a connection caching the pre-compiled statement of the given SQL + for (int i = 0; i < poolSize; i++) { + if (mPool.get(i).isFree() && mPool.get(i).mDb.isInStatementCache(sql)) { + poolObj = mPool.get(i); + break; + } + } + if (poolObj == null) { + // didn't find a free database connection with the given SQL already + // pre-compiled. return a free connection (this means, the same SQL could be + // pre-compiled on more than one database connection. potential wasted memory.) for (int i = 0; i < poolSize; i++) { - if (mPool.get(i).isFree() && mPool.get(i).mDb.isSqlInStatementCache(sql)) { + if (mPool.get(i).isFree()) { poolObj = mPool.get(i); break; } } - if (poolObj == null) { - // didn't find a free database connection with the given SQL already - // pre-compiled. return a free connection (this means, the same SQL could be - // pre-compiled on more than one database connection. potential wasted memory.) - for (int i = 0; i < poolSize; i++) { - if (mPool.get(i).isFree()) { - poolObj = mPool.get(i); - break; - } - } - } - db = poolObj.mDb; } - - assert poolObj != null; - assert poolObj.mDb == db; - - poolObj.acquire(); + db = poolObj.mDb; } + assert poolObj != null; + assert poolObj.mDb == db; + + poolObj.acquire(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "END get-connection: " + toString() + poolObj.toString()); } @@ -159,29 +154,25 @@ import java.util.Random; * release the given database connection back to the pool. * @param db the connection to be released */ - /* package */ void release(SQLiteDatabase db) { - PoolObj poolObj; - synchronized(mParentDbObj) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - assert db.mConnectionNum > 0; - doAsserts(); - assert mPool.get(db.mConnectionNum - 1).mDb == db; - } + /* package */ synchronized void release(SQLiteDatabase db) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + assert db.mConnectionNum > 0; + doAsserts(); + assert mPool.get(db.mConnectionNum - 1).mDb == db; + } - poolObj = mPool.get(db.mConnectionNum - 1); + PoolObj poolObj = mPool.get(db.mConnectionNum - 1); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "BEGIN release-conn: " + toString() + poolObj.toString()); - } - - if (poolObj.isFree()) { - throw new IllegalStateException("Releasing object already freed: " + - db.mConnectionNum); - } + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "BEGIN release-conn: " + toString() + poolObj.toString()); + } - poolObj.release(); + if (poolObj.isFree()) { + throw new IllegalStateException("Releasing object already freed: " + + db.mConnectionNum); } + poolObj.release(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "END release-conn: " + toString() + poolObj.toString()); } @@ -191,12 +182,10 @@ import java.util.Random; * Returns a list of all database connections in the pool (both free and busy connections). * This method is used when "adb bugreport" is done. */ - /* package */ ArrayList<SQLiteDatabase> getConnectionList() { + /* package */ synchronized ArrayList<SQLiteDatabase> getConnectionList() { ArrayList<SQLiteDatabase> list = new ArrayList<SQLiteDatabase>(); - synchronized(mParentDbObj) { - for (int i = mPool.size() - 1; i >= 0; i--) { - list.add(mPool.get(i).mDb); - } + for (int i = mPool.size() - 1; i >= 0; i--) { + list.add(mPool.get(i).mDb); } return list; } @@ -246,16 +235,14 @@ import java.util.Random; } } - /* package */ void setMaxPoolSize(int size) { - synchronized(mParentDbObj) { - mMaxPoolSize = size; - } + /** only used for testing purposes. */ + /* package */ synchronized void setMaxPoolSize(int size) { + mMaxPoolSize = size; } - /* package */ int getMaxPoolSize() { - synchronized(mParentDbObj) { - return mMaxPoolSize; - } + /** only used for testing purposes. */ + /* package */ synchronized int getMaxPoolSize() { + return mMaxPoolSize; } /** only used for testing purposes. */ diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java index 588384b..6ed1a90 100644 --- a/core/java/android/database/sqlite/SQLiteCompiledSql.java +++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java @@ -88,13 +88,9 @@ import android.util.Log; mInUse = false; } - /* package */ synchronized boolean isInUse() { - return mInUse; - } - /* package */ synchronized void releaseIfNotInUse() { // if it is not in use, release its memory from the database - if (!isInUse()) { + if (!mInUse) { releaseSqlStatement(); } } @@ -110,7 +106,7 @@ import android.util.Log; // but if the database itself is not closed and is GC'ed, then // all sub-objects attached to the database could end up getting GC'ed too. // in that case, don't print any warning. - if (!mInUse) { + if (mInUse) { int len = mSqlStmt.length(); Log.w(TAG, "Releasing statement in a finalizer. Please ensure " + "that you explicitly call close() on your cursor: " + diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 4cb7026..f0d0fb4 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -276,6 +276,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ // default statement-cache size per database connection ( = instance of this class) private int mMaxSqlCacheSize = 25; + // guarded by itself /* package */ final Map<String, SQLiteCompiledSql> mCompiledQueries = new LinkedHashMap<String, SQLiteCompiledSql>(mMaxSqlCacheSize + 1, 0.75f, true) { @Override @@ -306,8 +307,9 @@ public class SQLiteDatabase extends SQLiteClosable { public static final int MAX_SQL_CACHE_SIZE = 100; private boolean mCacheFullWarning; - /** maintain stats about number of cache hits and misses */ + /** Number of cache hits on this database connection. guarded by {@link #mCompiledQueries}. */ private int mNumCacheHits; + /** Number of cache misses on this database connection. guarded by {@link #mCompiledQueries}. */ private int mNumCacheMisses; /** Used to find out where this object was created in case it never got closed. */ @@ -344,6 +346,9 @@ public class SQLiteDatabase extends SQLiteClosable { private static final String MEMORY_DB_PATH = ":memory:"; + /** set to true if the database has attached databases */ + private volatile boolean mHasAttachedDbs = false; + /** stores reference to all databases opened in the current process. */ private static ArrayList<WeakReference<SQLiteDatabase>> mActiveDatabases = new ArrayList<WeakReference<SQLiteDatabase>>(); @@ -1049,13 +1054,18 @@ public class SQLiteDatabase extends SQLiteClosable { */ public void close() { if (!isOpen()) { - return; // already closed + return; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.i(TAG, "closing db: " + mPath + " (connection # " + mConnectionNum); } lock(); try { + // some other thread could have closed this database while I was waiting for lock. + // check the database state + if (!isOpen()) { + return; + } closeClosable(); // finalize ALL statements queued up so far closePendingStatements(); @@ -1831,6 +1841,9 @@ public class SQLiteDatabase extends SQLiteClosable { logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX); executeSql(sql, null); + if (stmtType == DatabaseUtils.STATEMENT_ATTACH) { + mHasAttachedDbs = true; + } // Log commit statements along with the most recently executed // SQL statement for disambiguation. if (stmtType == DatabaseUtils.STATEMENT_COMMIT) { @@ -2102,7 +2115,7 @@ public class SQLiteDatabase extends SQLiteClosable { return; } - if (!isCacheFullWarningLogged() && mCompiledQueries.size() == mMaxSqlCacheSize) { + if (!mCacheFullWarning && mCompiledQueries.size() == mMaxSqlCacheSize) { /* * cache size of {@link #mMaxSqlCacheSize} is not enough for this app. * log a warning. @@ -2110,7 +2123,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. "); - setCacheFullWarningLogged(); + mCacheFullWarning = true; } /* add the given SQLiteCompiledSql compiledStatement to cache. * no need to worry about the cache size - because {@link #mCompiledQueries} @@ -2134,14 +2147,16 @@ public class SQLiteDatabase extends SQLiteClosable { * From the compiledQueries cache, returns the compiled-statement-id for the given SQL. * Returns null, if not found in the cache. */ - /* package */ synchronized SQLiteCompiledSql getCompiledStatementForSql(String sql) { - SQLiteCompiledSql compiledStatement = mCompiledQueries.get(sql); - if (compiledStatement == null) { - mNumCacheMisses++; - return null; + /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) { + synchronized (mCompiledQueries) { + SQLiteCompiledSql compiledStatement = mCompiledQueries.get(sql); + if (compiledStatement == null) { + mNumCacheMisses++; + return null; + } + mNumCacheHits++; + return compiledStatement; } - mNumCacheHits++; - return compiledStatement; } /** @@ -2159,7 +2174,7 @@ public class SQLiteDatabase extends SQLiteClosable { * the value set with previous setMaxSqlCacheSize() call. */ public void setMaxSqlCacheSize(int cacheSize) { - synchronized(this) { + synchronized(mCompiledQueries) { if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE); } else if (cacheSize < mMaxSqlCacheSize) { @@ -2170,30 +2185,40 @@ public class SQLiteDatabase extends SQLiteClosable { } } - /* package */ boolean isSqlInStatementCache(String sql) { + /* package */ boolean isInStatementCache(String sql) { synchronized (mCompiledQueries) { return mCompiledQueries.containsKey(sql); } } - private synchronized boolean isCacheFullWarningLogged() { - return mCacheFullWarning; - } - - private synchronized void setCacheFullWarningLogged() { - mCacheFullWarning = true; + /* package */ void releaseCompiledSqlObj(SQLiteCompiledSql compiledSql) { + synchronized (mCompiledQueries) { + if (mCompiledQueries.containsValue(compiledSql)) { + // it is in cache - reset its inUse flag + compiledSql.release(); + } else { + // it is NOT in cache. finalize it. + compiledSql.releaseSqlStatement(); + } + } } - private synchronized int getCacheHitNum() { - return mNumCacheHits; + private int getCacheHitNum() { + synchronized(mCompiledQueries) { + return mNumCacheHits; + } } - private synchronized int getCacheMissNum() { - return mNumCacheMisses; + private int getCacheMissNum() { + synchronized(mCompiledQueries) { + return mNumCacheMisses; + } } - private synchronized int getCachesize() { - return mCompiledQueries.size(); + private int getCachesize() { + synchronized(mCompiledQueries) { + return mCompiledQueries.size(); + } } /* package */ void finalizeStatementLater(int id) { @@ -2279,26 +2304,34 @@ public class SQLiteDatabase extends SQLiteClosable { * * @return true if write-ahead-logging is set. false otherwise */ - public synchronized boolean enableWriteAheadLogging() { - if (mPath.equalsIgnoreCase(MEMORY_DB_PATH)) { - Log.i(TAG, "can't enable WAL for memory databases."); - return false; - } + public boolean enableWriteAheadLogging() { + // acquire lock - no that no other thread is enabling WAL at the same time + lock(); + try { + if (mConnectionPool != null) { + // already enabled + return true; + } + if (mPath.equalsIgnoreCase(MEMORY_DB_PATH)) { + Log.i(TAG, "can't enable WAL for memory databases."); + return false; + } - // make sure this database has NO attached databases because sqlite's write-ahead-logging - // doesn't work for databases with attached databases - if (getAttachedDbs().size() > 1) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, - "this database: " + mPath + " has attached databases. can't enable WAL."); + // make sure this database has NO attached databases because sqlite's write-ahead-logging + // doesn't work for databases with attached databases + if (mHasAttachedDbs) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, + "this database: " + mPath + " has attached databases. can't enable WAL."); + } + return false; } - return false; - } - if (mConnectionPool == null) { mConnectionPool = new DatabaseConnectionPool(this); setJournalMode(mPath, "WAL"); + return true; + } finally { + unlock(); } - return true; } /** @@ -2306,13 +2339,18 @@ public class SQLiteDatabase extends SQLiteClosable { * @hide */ public void disableWriteAheadLogging() { - synchronized (this) { + // grab database lock so that writeAheadLogging is not disabled from 2 different threads + // at the same time + lock(); + try { if (mConnectionPool == null) { - return; + return; // already disabled } mConnectionPool.close(); - mConnectionPool = null; setJournalMode(mPath, "TRUNCATE"); + mConnectionPool = null; + } finally { + unlock(); } } @@ -2338,32 +2376,6 @@ public class SQLiteDatabase extends SQLiteClosable { } } - /** - * Sets the database connection handle pool size to the given value. - * Database connection handle pool is enabled when the app calls - * {@link #enableWriteAheadLogging()}. - * <p> - * The default connection handle pool is set by the system by taking into account various - * aspects of the device, such as memory, number of cores etc. It is recommended that - * applications use the default pool size set by the system. - * - * @param size the value the connection handle pool size should be set to. - */ - public void setConnectionPoolSize(int size) { - synchronized(this) { - if (mConnectionPool == null) { - throw new IllegalStateException("connection pool not enabled"); - } - int i = mConnectionPool.getMaxPoolSize(); - if (size < i) { - throw new IllegalArgumentException( - "cannot set max pool size to a value less than the current max value(=" + - i + ")"); - } - mConnectionPool.setMaxPoolSize(size); - } - } - /* package */ SQLiteDatabase createPoolConnection(short connectionNum) { SQLiteDatabase db = openDatabase(mPath, mFactory, mFlags, mErrorHandler, connectionNum); db.mParentConnObj = this; @@ -2428,73 +2440,74 @@ public class SQLiteDatabase extends SQLiteClosable { */ /* package */ static ArrayList<DbStats> getDbStats() { ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>(); -// // make a local copy of mActiveDatabases - so that this method is not competing -// // for synchronization lock on mActiveDatabases -// ArrayList<WeakReference<SQLiteDatabase>> tempList; -// synchronized(mActiveDatabases) { -// tempList = (ArrayList<WeakReference<SQLiteDatabase>>)mActiveDatabases.clone(); -// } -// for (WeakReference<SQLiteDatabase> w : tempList) { -// SQLiteDatabase db = w.get(); -// if (db == null || !db.isOpen()) { -// continue; -// } -// -// synchronized (db) { -// try { -// // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db -// int lookasideUsed = db.native_getDbLookaside(); -// -// // get the lastnode of the dbname -// String path = db.getPath(); -// int indx = path.lastIndexOf("/"); -// String lastnode = path.substring((indx != -1) ? ++indx : 0); -// -// // get list of attached dbs and for each db, get its size and pagesize -// ArrayList<Pair<String, String>> attachedDbs = db.getAttachedDbs(); -// if (attachedDbs == null) { -// continue; -// } -// for (int i = 0; i < attachedDbs.size(); i++) { -// Pair<String, String> p = attachedDbs.get(i); -// long pageCount = DatabaseUtils.longForQuery(db, "PRAGMA " + p.first -// + ".page_count;", null); -// -// // first entry in the attached db list is always the main database -// // don't worry about prefixing the dbname with "main" -// String dbName; -// if (i == 0) { -// dbName = lastnode; -// } else { -// // lookaside is only relevant for the main db -// lookasideUsed = 0; -// dbName = " (attached) " + p.first; -// // if the attached db has a path, attach the lastnode from the path to above -// if (p.second.trim().length() > 0) { -// int idx = p.second.lastIndexOf("/"); -// dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0); -// } -// } -// if (pageCount > 0) { -// dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), -// lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(), -// db.getCachesize())); -// } -// } -// // if there are pooled connections, return the cache stats for them also. -// if (db.mConnectionPool != null) { -// for (SQLiteDatabase pDb : db.mConnectionPool.getConnectionList()) { -// dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") " -// + lastnode, 0, 0, 0, pDb.getCacheHitNum(), -// pDb.getCacheMissNum(), pDb.getCachesize())); -// } -// } -// } catch (SQLiteException e) { -// // ignore. we don't care about exceptions when we are taking adb -// // bugreport! -// } -// } -// } + // make a local copy of mActiveDatabases - so that this method is not competing + // for synchronization lock on mActiveDatabases + ArrayList<WeakReference<SQLiteDatabase>> tempList; + synchronized(mActiveDatabases) { + tempList = (ArrayList<WeakReference<SQLiteDatabase>>)mActiveDatabases.clone(); + } + for (WeakReference<SQLiteDatabase> w : tempList) { + SQLiteDatabase db = w.get(); + if (db == null || !db.isOpen()) { + continue; + } + + try { + // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db + int lookasideUsed = db.native_getDbLookaside(); + + // get the lastnode of the dbname + String path = db.getPath(); + int indx = path.lastIndexOf("/"); + String lastnode = path.substring((indx != -1) ? ++indx : 0); + + // get list of attached dbs and for each db, get its size and pagesize + ArrayList<Pair<String, String>> attachedDbs = db.getAttachedDbs(); + if (attachedDbs == null) { + continue; + } + for (int i = 0; i < attachedDbs.size(); i++) { + Pair<String, String> p = attachedDbs.get(i); + long pageCount = DatabaseUtils.longForQuery(db, "PRAGMA " + p.first + + ".page_count;", null); + + // first entry in the attached db list is always the main database + // don't worry about prefixing the dbname with "main" + String dbName; + if (i == 0) { + dbName = lastnode; + } else { + // lookaside is only relevant for the main db + lookasideUsed = 0; + dbName = " (attached) " + p.first; + // if the attached db has a path, attach the lastnode from the path to above + if (p.second.trim().length() > 0) { + int idx = p.second.lastIndexOf("/"); + dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0); + } + } + if (pageCount > 0) { + dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), + lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(), + db.getCachesize())); + } + } + // if there are pooled connections, return the cache stats for them also. + // while we are trying to query the pooled connections for stats, some other thread + // could be disabling conneciton pool. so, grab a reference to the connection pool. + DatabaseConnectionPool connPool = db.mConnectionPool; + if (connPool != null) { + for (SQLiteDatabase pDb : connPool.getConnectionList()) { + dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") " + + lastnode, 0, 0, 0, pDb.getCacheHitNum(), + pDb.getCacheMissNum(), pDb.getCachesize())); + } + } + } catch (SQLiteException e) { + // ignore. we don't care about exceptions when we are taking adb + // bugreport! + } + } return dbStatsList; } @@ -2510,6 +2523,20 @@ public class SQLiteDatabase extends SQLiteClosable { return null; } ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>(); + if (!mHasAttachedDbs) { + // No attached databases. + // There is a small window where attached databases exist but this flag is not set yet. + // This can occur when this thread is in a race condition with another thread + // that is executing the SQL statement: "attach database <blah> as <foo>" + // If this thread is NOT ok with such a race condition (and thus possibly not receive + // the entire list of attached databases), then the caller should ensure that no thread + // is executing any SQL statements while a thread is calling this method. + // Typically, this method is called when 'adb bugreport' is done or the caller wants to + // collect stats on the database and all its attached databases. + attachedDbs.add(new Pair<String, String>("main", mPath)); + return attachedDbs; + } + // has attached databases. query sqlite to get the list of attached databases. Cursor c = null; try { c = rawQuery("pragma database_list;", null); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 4747a9e..83621f2 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -25,7 +25,7 @@ import java.util.HashMap; /** * A base class for compiled SQLite programs. *<p> - * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple + * SQLiteProgram is NOT internally synchronized so code using a SQLiteProgram from multiple * threads should perform its own synchronization when using the SQLiteProgram. */ public abstract class SQLiteProgram extends SQLiteClosable { @@ -180,25 +180,11 @@ public abstract class SQLiteProgram extends SQLiteClosable { mDatabase.releaseReference(); } - /* package */ synchronized void release() { + /* package */ void release() { if (mCompiledSql == null) { return; } - if ((mStatementType & STATEMENT_CACHEABLE) == 0) { - // this SQL statement was never in cache - mCompiledSql.releaseSqlStatement(); - } else { - synchronized(mDatabase.mCompiledQueries) { - if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) { - // it is NOT in compiled-sql cache. i.e., responsibility of - // releasing this statement is on me. - mCompiledSql.releaseSqlStatement(); - } else { - // it is in compiled-sql cache. reset its CompiledSql#mInUse flag - mCompiledSql.release(); - } - } - } + mDatabase.releaseCompiledSqlObj(mCompiledSql); mCompiledSql = null; nStatement = 0; } @@ -241,34 +227,32 @@ public abstract class SQLiteProgram extends SQLiteClosable { } private void bind(int type, int index, Object value) { - synchronized (this) { - mDatabase.verifyDbIsOpen(); - addToBindArgs(index, (type == Cursor.FIELD_TYPE_NULL) ? null : value); - if (nStatement > 0) { - // bind only if the SQL statement is compiled - acquireReference(); - try { - switch (type) { - case Cursor.FIELD_TYPE_NULL: - native_bind_null(index); - break; - case Cursor.FIELD_TYPE_BLOB: - native_bind_blob(index, (byte[]) value); - break; - case Cursor.FIELD_TYPE_FLOAT: - native_bind_double(index, (Double) value); - break; - case Cursor.FIELD_TYPE_INTEGER: - native_bind_long(index, (Long) value); - break; - case Cursor.FIELD_TYPE_STRING: - default: - native_bind_string(index, (String) value); - break; - } - } finally { - releaseReference(); + mDatabase.verifyDbIsOpen(); + addToBindArgs(index, (type == Cursor.FIELD_TYPE_NULL) ? null : value); + if (nStatement > 0) { + // bind only if the SQL statement is compiled + acquireReference(); + try { + switch (type) { + case Cursor.FIELD_TYPE_NULL: + native_bind_null(index); + break; + case Cursor.FIELD_TYPE_BLOB: + native_bind_blob(index, (byte[]) value); + break; + case Cursor.FIELD_TYPE_FLOAT: + native_bind_double(index, (Double) value); + break; + case Cursor.FIELD_TYPE_INTEGER: + native_bind_long(index, (Long) value); + break; + case Cursor.FIELD_TYPE_STRING: + default: + native_bind_string(index, (String) value); + break; } + } finally { + releaseReference(); } } } @@ -337,18 +321,16 @@ public abstract class SQLiteProgram extends SQLiteClosable { * Clears all existing bindings. Unset bindings are treated as NULL. */ public void clearBindings() { - synchronized (this) { - mBindArgs = null; - if (this.nStatement == 0) { - return; - } - mDatabase.verifyDbIsOpen(); - acquireReference(); - try { - native_clear_bindings(); - } finally { - releaseReference(); - } + mBindArgs = null; + if (this.nStatement == 0) { + return; + } + mDatabase.verifyDbIsOpen(); + acquireReference(); + try { + native_clear_bindings(); + } finally { + releaseReference(); } } @@ -356,23 +338,21 @@ public abstract class SQLiteProgram extends SQLiteClosable { * Release this program's resources, making it invalid. */ public void close() { - synchronized (this) { - mBindArgs = null; - if (nHandle == 0 || !mDatabase.isOpen()) { - return; - } - releaseReference(); + mBindArgs = null; + if (nHandle == 0 || !mDatabase.isOpen()) { + return; } + releaseReference(); } - private synchronized void addToBindArgs(int index, Object value) { + private void addToBindArgs(int index, Object value) { if (mBindArgs == null) { mBindArgs = new HashMap<Integer, Object>(); } mBindArgs.put(index, value); } - /* package */ synchronized void compileAndbindAllArgs() { + /* package */ void compileAndbindAllArgs() { if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) { // no need to prepare this SQL statement if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { @@ -424,10 +404,8 @@ public abstract class SQLiteProgram extends SQLiteClosable { return; } int size = bindArgs.length; - synchronized(this) { - for (int i = 0; i < size; i++) { - bindString(i + 1, bindArgs[i]); - } + for (int i = 0; i < size; i++) { + bindString(i + 1, bindArgs[i]); } } diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index bd05e24..5e96928 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -31,7 +31,7 @@ import dalvik.system.BlockGuard; * Don't use SQLiteStatement constructor directly, please use * {@link SQLiteDatabase#compileStatement(String)} *<p> - * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple + * SQLiteStatement is NOT internally synchronized so code using a SQLiteStatement from multiple * threads should perform its own synchronization when using the SQLiteStatement. */ @SuppressWarnings("deprecation") @@ -79,23 +79,21 @@ public class SQLiteStatement extends SQLiteProgram * some reason */ public int executeUpdateDelete() { - synchronized(this) { - try { - long timeStart = acquireAndLock(WRITE); - int numChanges = 0; - if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) { - // since the statement doesn't have to be prepared, - // call the following native method which will not prepare - // the query plan - native_executeSql(mSql); - } else { - numChanges = native_execute(); - } - mDatabase.logTimeStat(mSql, timeStart); - return numChanges; - } finally { - releaseAndUnlock(); + try { + long timeStart = acquireAndLock(WRITE); + int numChanges = 0; + if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) { + // since the statement doesn't have to be prepared, + // call the following native method which will not prepare + // the query plan + native_executeSql(mSql); + } else { + numChanges = native_execute(); } + mDatabase.logTimeStat(mSql, timeStart); + return numChanges; + } finally { + releaseAndUnlock(); } } @@ -109,15 +107,13 @@ public class SQLiteStatement extends SQLiteProgram * some reason */ public long executeInsert() { - synchronized(this) { - try { - long timeStart = acquireAndLock(WRITE); - long lastInsertedRowId = native_executeInsert(); - mDatabase.logTimeStat(mSql, timeStart); - return lastInsertedRowId; - } finally { - releaseAndUnlock(); - } + try { + long timeStart = acquireAndLock(WRITE); + long lastInsertedRowId = native_executeInsert(); + mDatabase.logTimeStat(mSql, timeStart); + return lastInsertedRowId; + } finally { + releaseAndUnlock(); } } @@ -130,15 +126,13 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public long simpleQueryForLong() { - synchronized(this) { - try { - long timeStart = acquireAndLock(READ); - long retValue = native_1x1_long(); - mDatabase.logTimeStat(mSql, timeStart); - return retValue; - } finally { - releaseAndUnlock(); - } + try { + long timeStart = acquireAndLock(READ); + long retValue = native_1x1_long(); + mDatabase.logTimeStat(mSql, timeStart); + return retValue; + } finally { + releaseAndUnlock(); } } @@ -151,15 +145,13 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public String simpleQueryForString() { - synchronized(this) { - try { - long timeStart = acquireAndLock(READ); - String retValue = native_1x1_string(); - mDatabase.logTimeStat(mSql, timeStart); - return retValue; - } finally { - releaseAndUnlock(); - } + try { + long timeStart = acquireAndLock(READ); + String retValue = native_1x1_string(); + mDatabase.logTimeStat(mSql, timeStart); + return retValue; + } finally { + releaseAndUnlock(); } } @@ -172,18 +164,16 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() { - synchronized(this) { - try { - long timeStart = acquireAndLock(READ); - ParcelFileDescriptor retValue = native_1x1_blob_ashmem(); - mDatabase.logTimeStat(mSql, timeStart); - return retValue; - } catch (IOException ex) { - Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex); - return null; - } finally { - releaseAndUnlock(); - } + try { + long timeStart = acquireAndLock(READ); + ParcelFileDescriptor retValue = native_1x1_blob_ashmem(); + mDatabase.logTimeStat(mSql, timeStart); + return retValue; + } catch (IOException ex) { + Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex); + return null; + } finally { + releaseAndUnlock(); } } diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index 140b71f..5fd0d89 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -88,13 +88,23 @@ public class ProxyProperties implements Parcelable { * @hide */ public void writeToParcel(Parcel dest, int flags) { + String host = null; if (mProxy != null) { - InetAddress addr = mProxy.getAddress(); - if (addr != null) { - dest.writeByte((byte)1); - dest.writeByteArray(addr.getAddress()); - dest.writeInt(mProxy.getPort()); - } + try { + InetAddress addr = mProxy.getAddress(); + if (addr != null) { + host = addr.getHostAddress(); + } else { + /* Does not resolve when addr is null */ + host = mProxy.getHostName(); + } + } catch (Exception e) { } + } + + if (host != null) { + dest.writeByte((byte)1); + dest.writeString(host); + dest.writeInt(mProxy.getPort()); } else { dest.writeByte((byte)0); } @@ -111,9 +121,11 @@ public class ProxyProperties implements Parcelable { ProxyProperties proxyProperties = new ProxyProperties(); if (in.readByte() == 1) { try { - InetAddress addr = InetAddress.getByAddress(in.createByteArray()); - proxyProperties.setSocketAddress(new InetSocketAddress(addr, in.readInt())); - } catch (UnknownHostException e) { } + String host = in.readString(); + int port = in.readInt(); + proxyProperties.setSocketAddress(InetSocketAddress.createUnresolved( + host, port)); + } catch (IllegalArgumentException e) { } } proxyProperties.setExclusionList(in.readString()); return proxyProperties; diff --git a/core/java/android/os/storage/OnObbStateChangeListener.java b/core/java/android/os/storage/OnObbStateChangeListener.java new file mode 100644 index 0000000..a2d0a56 --- /dev/null +++ b/core/java/android/os/storage/OnObbStateChangeListener.java @@ -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. + */ + +package android.os.storage; + +/** + * Used for receiving notifications from {@link StorageManager}. + */ +public abstract class OnObbStateChangeListener { + /** + * Called when an OBB has changed states. + * + * @param path path to the OBB file the state change has happened on + * @param state the current state of the OBB + */ + public void onObbStateChange(String path, String state) { + } +} diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 4a0296b..4268618 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -23,14 +23,28 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; /** - * StorageManager is the interface to the systems storage service. + * StorageManager is the interface to the systems storage service. The storage + * manager handles storage-related items such as Opaque Binary Blobs (OBBs). + * <p> + * OBBs contain a filesystem that maybe be encrypted on disk and mounted + * on-demand from an application. OBBs are a good way of providing large amounts + * of binary assets without packaging them into APKs as they may be multiple + * gigabytes in size. However, due to their size, they're most likely stored in + * a shared storage pool accessible from all programs. The system does not + * guarantee the security of the OBB file itself: if any program modifies the + * OBB, there is no guarantee that a read from that OBB will produce the + * expected output. + * <p> * Get an instance of this class by calling - * {@link android.content.Context#getSystemService(java.lang.String)} with an argument - * of {@link android.content.Context#STORAGE_SERVICE}. - * + * {@link android.content.Context#getSystemService(java.lang.String)} with an + * argument of {@link android.content.Context#STORAGE_SERVICE}. */ public class StorageManager @@ -76,11 +90,113 @@ public class StorageManager /** * Binder listener for OBB action results. */ - private final ObbActionBinderListener mObbActionListener = new ObbActionBinderListener(); - private class ObbActionBinderListener extends IObbActionListener.Stub { + private final ObbActionListener mObbActionListener = new ObbActionListener(); + + private class ObbActionListener extends IObbActionListener.Stub { + private List<WeakReference<ObbListenerDelegate>> mListeners = new LinkedList<WeakReference<ObbListenerDelegate>>(); + @Override public void onObbResult(String filename, String status) throws RemoteException { - Log.i(TAG, "filename = " + filename + ", result = " + status); + synchronized (mListeners) { + final Iterator<WeakReference<ObbListenerDelegate>> iter = mListeners.iterator(); + while (iter.hasNext()) { + final WeakReference<ObbListenerDelegate> ref = iter.next(); + + final ObbListenerDelegate delegate = (ref == null) ? null : ref.get(); + if (delegate == null) { + iter.remove(); + continue; + } + + delegate.sendObbStateChanged(filename, status); + } + } + } + + public void addListener(OnObbStateChangeListener listener) { + if (listener == null) { + return; + } + + synchronized (mListeners) { + final Iterator<WeakReference<ObbListenerDelegate>> iter = mListeners.iterator(); + while (iter.hasNext()) { + final WeakReference<ObbListenerDelegate> ref = iter.next(); + + final ObbListenerDelegate delegate = (ref == null) ? null : ref.get(); + if (delegate == null) { + iter.remove(); + continue; + } + + /* + * If we're already in the listeners, we don't need to be in + * there again. + */ + if (listener.equals(delegate.getListener())) { + return; + } + } + + final ObbListenerDelegate delegate = new ObbListenerDelegate(listener); + mListeners.add(new WeakReference<ObbListenerDelegate>(delegate)); + } + } + } + + /** + * Private class containing sender and receiver code for StorageEvents. + */ + private class ObbListenerDelegate { + private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef; + private final Handler mHandler; + + ObbListenerDelegate(OnObbStateChangeListener listener) { + mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener); + mHandler = new Handler(mTgtLooper) { + @Override + public void handleMessage(Message msg) { + final OnObbStateChangeListener listener = getListener(); + if (listener == null) { + return; + } + + StorageEvent e = (StorageEvent) msg.obj; + + if (msg.what == StorageEvent.EVENT_OBB_STATE_CHANGED) { + ObbStateChangedStorageEvent ev = (ObbStateChangedStorageEvent) e; + listener.onObbStateChange(ev.path, ev.state); + } else { + Log.e(TAG, "Unsupported event " + msg.what); + } + } + }; + } + + OnObbStateChangeListener getListener() { + if (mObbEventListenerRef == null) { + return null; + } + return mObbEventListenerRef.get(); + } + + void sendObbStateChanged(String path, String state) { + ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state); + mHandler.sendMessage(e.getMessage()); + } + } + + /** + * Message sent during an OBB status change event. + */ + private class ObbStateChangedStorageEvent extends StorageEvent { + public final String path; + public final String state; + + public ObbStateChangedStorageEvent(String path, String state) { + super(EVENT_OBB_STATE_CHANGED); + this.path = path; + this.state = state; } } @@ -89,8 +205,9 @@ public class StorageManager * and the target looper handler. */ private class StorageEvent { - public static final int EVENT_UMS_CONNECTION_CHANGED = 1; - public static final int EVENT_STORAGE_STATE_CHANGED = 2; + static final int EVENT_UMS_CONNECTION_CHANGED = 1; + static final int EVENT_STORAGE_STATE_CHANGED = 2; + static final int EVENT_OBB_STATE_CHANGED = 3; private Message mMessage; @@ -291,19 +408,27 @@ public class StorageManager * specified, it is supplied to the mounting process to be used in any * encryption used in the OBB. * <p> + * The OBB will remain mounted for as long as the StorageManager reference + * is held by the application. As soon as this reference is lost, the OBBs + * in use will be unmounted. The {@link OnObbStateChangeListener} registered with + * this call will receive all further OBB-related events until it goes out + * of scope. If the caller is not interested in whether the call succeeds, + * the <code>listener</code> may be specified as <code>null</code>. + * <p> * <em>Note:</em> you can only mount OBB files for which the OBB tag on the * file matches a package ID that is owned by the calling program's UID. - * That is, shared UID applications can obtain access to any other + * That is, shared UID applications can attempt to mount any other * application's OBB that shares its UID. - * <p> - * STOPSHIP document more; discuss lack of guarantees of security * * @param filename the path to the OBB file - * @param key decryption key + * @param key secret used to encrypt the OBB; may be <code>null</code> if no + * encryption was used on the OBB. * @return whether the mount call was successfully queued or not + * @throws IllegalArgumentException when the OBB is already mounted */ - public boolean mountObb(String filename, String key) { + public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) { try { + mObbActionListener.addListener(listener); mMountService.mountObb(filename, key, mObbActionListener); return true; } catch (RemoteException e) { @@ -314,15 +439,20 @@ public class StorageManager } /** - * Unmount an Opaque Binary Blob (OBB) file. If the <code>force</code> flag - * is true, it will kill any application needed to unmount the given OBB. + * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the + * <code>force</code> flag is true, it will kill any application needed to + * unmount the given OBB (even the calling application). + * <p> + * The {@link OnObbStateChangeListener} registered with this call will receive all + * further OBB-related events until it goes out of scope. If the caller is + * not interested in whether the call succeeded, the listener may be + * specified as <code>null</code>. * <p> * <em>Note:</em> you can only mount OBB files for which the OBB tag on the * file matches a package ID that is owned by the calling program's UID. * That is, shared UID applications can obtain access to any other * application's OBB that shares its UID. * <p> - * STOPSHIP document more; discuss lack of guarantees of security * * @param filename path to the OBB file * @param force whether to kill any programs using this in order to unmount @@ -330,8 +460,10 @@ public class StorageManager * @return whether the unmount call was successfully queued or not * @throws IllegalArgumentException when OBB is not already mounted */ - public boolean unmountObb(String filename, boolean force) throws IllegalArgumentException { + public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) + throws IllegalArgumentException { try { + mObbActionListener.addListener(listener); mMountService.unmountObb(filename, force, mObbActionListener); return true; } catch (RemoteException e) { diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java index 276bddc..03bc41a 100644 --- a/core/java/android/provider/BrowserContract.java +++ b/core/java/android/provider/BrowserContract.java @@ -21,10 +21,12 @@ import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.RemoteException; -import android.provider.SyncStateContract; import android.util.Pair; /** @@ -493,4 +495,61 @@ public class BrowserContract { */ public static final String IS_BOOKMARK = "bookmark"; } + + /** + * A table that stores settings specific to the browser. Only support query and insert. + */ + public static final class Settings { + /** + * This utility class cannot be instantiated + */ + private Settings() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "settings"); + + /** + * Key for a setting value. + */ + public static final String KEY = "key"; + + /** + * Value for a setting. + */ + public static final String VALUE = "value"; + + /** + * If set to non-0 the user has opted into bookmark sync. + */ + public static final String KEY_SYNC_ENABLED = "sync_enabled"; + + /** + * Returns true if bookmark sync is enabled + */ + static public boolean isSyncEnabled(Context context) { + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(CONTENT_URI, new String[] { VALUE }, + KEY + "=?", new String[] { KEY_SYNC_ENABLED }, null); + if (cursor == null || !cursor.moveToFirst()) { + return false; + } + return cursor.getInt(0) != 0; + } finally { + if (cursor != null) cursor.close(); + } + } + + /** + * Sets the bookmark sync enabled setting. + */ + static public void setSyncEnabled(Context context, boolean enabled) { + ContentValues values = new ContentValues(); + values.put(KEY, KEY_SYNC_ENABLED); + values.put(VALUE, enabled ? 1 : 0); + context.getContentResolver().insert(CONTENT_URI, values); + } + } } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index da02845..b74e76f 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -1234,6 +1234,19 @@ public final class MediaStore { } /** + * Get the content:// style URI for querying the genres of an audio file. + * + * @param volumeName the name of the volume to get the URI for + * @param audioId the ID of the audio file for which to retrieve the genres + * @return the URI to for querying the genres for the audio file + * with the given the volume and audioID + */ + public static Uri getContentUriForAudioId(String volumeName, int audioId) { + return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + + "/audio/media/" + audioId + "/genres"); + } + + /** * The content:// style URI for the internal storage. */ public static final Uri INTERNAL_CONTENT_URI = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e98fa26..cf95872 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -350,6 +350,20 @@ public final class Settings { "android.settings.MANAGE_APPLICATIONS_SETTINGS"; /** + * Activity Action: Show settings to manage all applications. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = + "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; + + /** * Activity Action: Show screen of details about a particular application. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -1628,6 +1642,86 @@ public final class Settings { public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse"; /** + * Whether nfc is enabled/disabled + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_ON = "nfc_on"; + + /** + * Whether nfc secure element is enabled/disabled + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_SECURE_ELEMENT_ON = "nfc_secure_element_on"; + + /** + * Whether nfc secure element is enabled/disabled + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_SECURE_ELEMENT_ID = "nfc_secure_element_id"; + + /** + * LLCP LTO value + * @hide + */ + public static final String NFC_LLCP_LTO = "nfc_llcp_lto"; + + /** + * LLCP MIU value + * @hide + */ + public static final String NFC_LLCP_MIU = "nfc_llcp_miu"; + + /** + * LLCP WKS value + * @hide + */ + public static final String NFC_LLCP_WKS = "nfc_llcp_wks"; + + /** + * LLCP OPT value + * @hide + */ + public static final String NFC_LLCP_OPT = "nfc_llcp_opt"; + + /** + * NFC Discovery Reader A + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_DISCOVERY_A = "nfc_discovery_a"; + + /** + * NFC Discovery Reader B + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_DISCOVERY_B = "nfc_discovery_b"; + + /** + * NFC Discovery Reader Felica + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_DISCOVERY_F = "nfc_discovery_felica"; + + /** + * NFC Discovery Reader 15693 + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_DISCOVERY_15693 = "nfc_discovery_15693"; + + /** + * NFC Discovery NFCIP + * 0=disabled. 1=enabled. + * @hide + */ + public static final String NFC_DISCOVERY_NFCIP = "nfc_discovery_nfcip"; + + /** * Show pointer location on screen? * 0 = no * 1 = yes @@ -1800,7 +1894,19 @@ public final class Settings { NOTIFICATION_LIGHT_PULSE, USE_PTP_INTERFACE, SIP_CALL_OPTIONS, - SIP_RECEIVE_CALLS + SIP_RECEIVE_CALLS, + NFC_ON, + NFC_SECURE_ELEMENT_ON, + NFC_SECURE_ELEMENT_ID, + NFC_LLCP_LTO, + NFC_LLCP_MIU, + NFC_LLCP_WKS, + NFC_LLCP_OPT, + NFC_DISCOVERY_A, + NFC_DISCOVERY_B, + NFC_DISCOVERY_F, + NFC_DISCOVERY_15693, + NFC_DISCOVERY_NFCIP, }; // Settings moved to Settings.Secure diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index a52a221..946c266 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -25,6 +25,7 @@ package android.server; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothA2dp; import android.content.BroadcastReceiver; @@ -32,17 +33,15 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; -import android.os.Handler; -import android.os.Message; import android.os.ParcelUuid; import android.provider.Settings; import android.util.Log; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; + public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private static final String TAG = "BluetoothA2dpService"; @@ -57,11 +56,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private static final String PROPERTY_STATE = "State"; - private static final String SINK_STATE_DISCONNECTED = "disconnected"; - private static final String SINK_STATE_CONNECTING = "connecting"; - private static final String SINK_STATE_CONNECTED = "connected"; - private static final String SINK_STATE_PLAYING = "playing"; - private static int mSinkCount; private final Context mContext; @@ -72,6 +66,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private final BluetoothAdapter mAdapter; private int mTargetA2dpState; private boolean mAdjustedPriority = false; + private BluetoothDevice mPlayingA2dpDevice; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -95,12 +90,12 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { BluetoothDevice.ERROR); switch(bondState) { case BluetoothDevice.BOND_BONDED: - if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) { - setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); + if (getPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) { + setPriority(device, BluetoothA2dp.PRIORITY_ON); } break; case BluetoothDevice.BOND_NONE: - setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED); + setPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED); break; } } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { @@ -113,7 +108,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); if (streamType == AudioManager.STREAM_MUSIC) { - BluetoothDevice sinks[] = getConnectedSinks(); + BluetoothDevice sinks[] = getConnectedDevices(); + if (sinks.length != 0 && isPhoneDocked(sinks[0])) { String address = sinks[0].getAddress(); int newVolLevel = @@ -254,7 +250,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()]; devices = mAudioDevices.keySet().toArray(devices); for (BluetoothDevice device : devices) { - int state = getSinkState(device); + int state = getConnectionState(device); switch (state) { case BluetoothA2dp.STATE_CONNECTING: case BluetoothA2dp.STATE_CONNECTED: @@ -277,7 +273,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) { if (!mBluetoothService.isEnabled() || !isSinkDevice(device) || - getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) { + getPriority(device) == BluetoothA2dp.PRIORITY_OFF) { return false; } @@ -292,12 +288,26 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return true; } - public synchronized boolean connectSink(BluetoothDevice device) { + public synchronized boolean isA2dpPlaying(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (DBG) log("isA2dpPlaying(" + device + ")"); + if (device.equals(mPlayingA2dpDevice)) return true; + return false; + } + + public synchronized boolean connect(BluetoothDevice device) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (DBG) log("connectSink(" + device + ")"); if (!isConnectSinkFeasible(device)) return false; + for (BluetoothDevice sinkDevice : mAudioDevices.keySet()) { + if (getConnectionState(sinkDevice) != BluetoothProfile.STATE_DISCONNECTED) { + disconnect(sinkDevice); + } + } + return mBluetoothService.connectSink(device.getAddress()); } @@ -307,17 +317,15 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { int state = mAudioDevices.get(device); // ignore if there are any active sinks - if (lookupSinksMatchingStates(new int[] { + if (getDevicesMatchingConnectionStates(new int[] { BluetoothA2dp.STATE_CONNECTING, BluetoothA2dp.STATE_CONNECTED, - BluetoothA2dp.STATE_PLAYING, - BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) { + BluetoothA2dp.STATE_DISCONNECTING}).length != 0) { return false; } switch (state) { case BluetoothA2dp.STATE_CONNECTED: - case BluetoothA2dp.STATE_PLAYING: case BluetoothA2dp.STATE_DISCONNECTING: return false; case BluetoothA2dp.STATE_CONNECTING: @@ -343,17 +351,16 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return false; } - int state = getSinkState(device); + int state = getConnectionState(device); switch (state) { case BluetoothA2dp.STATE_DISCONNECTED: - return false; case BluetoothA2dp.STATE_DISCONNECTING: - return true; + return false; } return true; } - public synchronized boolean disconnectSink(BluetoothDevice device) { + public synchronized boolean disconnect(BluetoothDevice device) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (DBG) log("disconnectSink(" + device + ")"); @@ -362,7 +369,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } public synchronized boolean disconnectSinkInternal(BluetoothDevice device) { - int state = getSinkState(device); + int state = getConnectionState(device); String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); // State is CONNECTING or CONNECTED or PLAYING @@ -408,44 +415,49 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return checkSinkSuspendState(state.intValue()); } - public synchronized BluetoothDevice[] getConnectedSinks() { + public synchronized int getConnectionState(BluetoothDevice device) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - Set<BluetoothDevice> sinks = lookupSinksMatchingStates( - new int[] {BluetoothA2dp.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING}); - return sinks.toArray(new BluetoothDevice[sinks.size()]); + Integer state = mAudioDevices.get(device); + if (state == null) + return BluetoothA2dp.STATE_DISCONNECTED; + return state; } - public synchronized BluetoothDevice[] getNonDisconnectedSinks() { + public synchronized BluetoothDevice[] getConnectedDevices() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - Set<BluetoothDevice> sinks = lookupSinksMatchingStates( - new int[] {BluetoothA2dp.STATE_CONNECTED, - BluetoothA2dp.STATE_PLAYING, - BluetoothA2dp.STATE_CONNECTING, - BluetoothA2dp.STATE_DISCONNECTING}); - return sinks.toArray(new BluetoothDevice[sinks.size()]); + BluetoothDevice[] sinks = getDevicesMatchingConnectionStates( + new int[] {BluetoothA2dp.STATE_CONNECTED}); + return sinks; } - public synchronized int getSinkState(BluetoothDevice device) { + public synchronized BluetoothDevice[] getDevicesMatchingConnectionStates(int[] states) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - Integer state = mAudioDevices.get(device); - if (state == null) - return BluetoothA2dp.STATE_DISCONNECTED; - return state; + ArrayList<BluetoothDevice> sinks = new ArrayList(); + if (mAudioDevices.isEmpty()) { + return sinks.toArray(new BluetoothDevice[sinks.size()]); + } + for (BluetoothDevice device: mAudioDevices.keySet()) { + int sinkState = getConnectionState(device); + for (int state : states) { + if (state == sinkState) { + sinks.add(device); + break; + } + } + } + return sinks.toArray(new BluetoothDevice[sinks.size()]); } - public synchronized int getSinkPriority(BluetoothDevice device) { + public synchronized int getPriority(BluetoothDevice device) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), BluetoothA2dp.PRIORITY_UNDEFINED); } - public synchronized boolean setSinkPriority(BluetoothDevice device, int priority) { + public synchronized boolean setPriority(BluetoothDevice device, int priority) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) { - return false; - } return Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority); } @@ -471,8 +483,17 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { // We have authorized it and bluez state has changed. addAudioSink(device); } else { - int prevState = mAudioDevices.get(device); - handleSinkStateChange(device, prevState, state); + if (state == BluetoothA2dp.STATE_PLAYING && mPlayingA2dpDevice == null) { + mPlayingA2dpDevice = device; + handleSinkPlayingStateChange(device, state, BluetoothA2dp.STATE_NOT_PLAYING); + } else if (state == BluetoothA2dp.STATE_CONNECTED && mPlayingA2dpDevice != null) { + mPlayingA2dpDevice = null; + handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING, + BluetoothA2dp.STATE_PLAYING); + } else { + int prevState = mAudioDevices.get(device); + handleSinkStateChange(device, prevState, state); + } } } } @@ -484,18 +505,19 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mSinkCount--; } else if (state == BluetoothA2dp.STATE_CONNECTED) { mSinkCount ++; + mPlayingA2dpDevice = null; } mAudioDevices.put(device, state); checkSinkSuspendState(state); mTargetA2dpState = -1; - if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF && + if (getPriority(device) > BluetoothA2dp.PRIORITY_OFF && state == BluetoothA2dp.STATE_CONNECTING || state == BluetoothA2dp.STATE_CONNECTED) { // We have connected or attempting to connect. // Bump priority - setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); + setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); } if (state == BluetoothA2dp.STATE_CONNECTED) { @@ -504,45 +526,38 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { adjustOtherSinkPriorities(device); } - Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState); - intent.putExtra(BluetoothA2dp.EXTRA_SINK_STATE, state); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, state); mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); } } + private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) { + Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, state); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + + if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state); + } + private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { if (!mAdjustedPriority) { for (BluetoothDevice device : mAdapter.getBondedDevices()) { - if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT && + if (getPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT && !device.equals(connectedDevice)) { - setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); + setPriority(device, BluetoothA2dp.PRIORITY_ON); } } mAdjustedPriority = true; } } - private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) { - Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>(); - if (mAudioDevices.isEmpty()) { - return sinks; - } - for (BluetoothDevice device: mAudioDevices.keySet()) { - int sinkState = getSinkState(device); - for (int state : states) { - if (state == sinkState) { - sinks.add(device); - break; - } - } - } - return sinks; - } - private boolean checkSinkSuspendState(int state) { boolean result = true; @@ -568,7 +583,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); if (address == null) return; BluetoothDevice device = mAdapter.getRemoteDevice(address); - int state = getSinkState(device); + int state = getConnectionState(device); handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); } } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index ab79aaf..05cbeff 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -22,6 +22,7 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothInputDevice; import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.content.Context; import android.content.Intent; @@ -54,6 +55,7 @@ class BluetoothEventLoop { private final HashMap<String, Integer> mPasskeyAgentRequestData; private final BluetoothService mBluetoothService; private final BluetoothAdapter mAdapter; + private BluetoothA2dp mA2dp; private final Context mContext; // The WakeLock is used for bringing up the LCD during a pairing request // from remote device when Android is in Suspend state. @@ -118,8 +120,21 @@ class BluetoothEventLoop { | PowerManager.ON_AFTER_RELEASE, TAG); mWakeLock.setReferenceCounted(false); initializeNativeDataNative(); + + mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP); } + private BluetoothProfile.ServiceListener mProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mA2dp = (BluetoothA2dp) proxy; + } + public void onServiceDisconnected(int profile) { + mA2dp = null; + } + }; + + protected void finalize() throws Throwable { try { cleanupNativeDataNative(); @@ -574,12 +589,11 @@ class BluetoothEventLoop { // Bluez sends the UUID of the local service being accessed, _not_ the // remote service - if ((BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) + if (mA2dp != null && + (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) && - !isOtherSinkInNonDisconnectingState(address)) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - - authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; + !isOtherSinkInNonDisconnectedState(address)) { + authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF; if (authorized) { Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address); mBluetoothService.notifyIncomingA2dpConnection(address); @@ -630,9 +644,12 @@ class BluetoothEventLoop { return false; } - private boolean isOtherSinkInNonDisconnectingState(String address) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks(); + private boolean isOtherSinkInNonDisconnectedState(String address) { + Set<BluetoothDevice> devices = + mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED, + BluetoothA2dp.STATE_CONNECTING, + BluetoothA2dp.STATE_DISCONNECTING}); + if (devices.size() == 0) return false; for(BluetoothDevice dev: devices) { if (!dev.getAddress().equals(address)) return true; diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 7f160c4..bd105a7 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -30,6 +30,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothDeviceProfileState; import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfileState; import android.bluetooth.BluetoothInputDevice; import android.bluetooth.BluetoothSocket; @@ -88,6 +89,7 @@ public class BluetoothService extends IBluetooth.Stub { private int mNativeData; private BluetoothEventLoop mEventLoop; + private BluetoothHeadset mBluetoothHeadset; private boolean mIsAirplaneSensitive; private boolean mIsAirplaneToggleable; private int mBluetoothState; @@ -2434,7 +2436,8 @@ public class BluetoothService extends IBluetooth.Stub { pw.println("Local name = " + getName()); pw.println("isDiscovering() = " + isDiscovering()); - BluetoothHeadset headset = new BluetoothHeadset(mContext, null); + mAdapter.getProfileProxy(mContext, + mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); pw.println("\n--Known devices--"); for (String address : mDeviceProperties.keySet()) { @@ -2479,24 +2482,48 @@ public class BluetoothService extends IBluetooth.Stub { // Rather not do this from here, but no-where else and I need this // dump pw.println("\n--Headset Service--"); - switch (headset.getState(headset.getCurrentHeadset())) { - case BluetoothHeadset.STATE_DISCONNECTED: - pw.println("getState() = STATE_DISCONNECTED"); - break; - case BluetoothHeadset.STATE_CONNECTING: - pw.println("getState() = STATE_CONNECTING"); - break; - case BluetoothHeadset.STATE_CONNECTED: - pw.println("getState() = STATE_CONNECTED"); - break; - case BluetoothHeadset.STATE_ERROR: - pw.println("getState() = STATE_ERROR"); - break; + if (mBluetoothHeadset != null) { + Set<BluetoothDevice> deviceSet = mBluetoothHeadset.getConnectedDevices(); + if (deviceSet.size() == 0) { + pw.println("\n--No headsets connected--"); + } + BluetoothDevice device = (BluetoothDevice) deviceSet.toArray()[0]; + + switch (mBluetoothHeadset.getConnectionState(device)) { + case BluetoothHeadset.STATE_DISCONNECTED: + pw.println("getConnectionState() = STATE_DISCONNECTED"); + break; + case BluetoothHeadset.STATE_CONNECTING: + pw.println("getConnectionState() = STATE_CONNECTING"); + break; + case BluetoothHeadset.STATE_CONNECTED: + pw.println("getConnectionState() = STATE_CONNECTED"); + break; + case BluetoothHeadset.STATE_DISCONNECTING: + pw.println("getConnectionState() = STATE_DISCONNECTING"); + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + pw.println("getConnectionState() = STATE_AUDIO_CONNECTED"); + break; + } + + deviceSet.clear(); + deviceSet = mBluetoothHeadset.getDevicesMatchingConnectionStates(new int[] { + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED}); + pw.println("\n--Connected and Disconnected Headsets"); + for (BluetoothDevice dev: deviceSet) { + pw.println(device); + if (mBluetoothHeadset.isAudioConnected(device)) { + pw.println("SCO audio connected to device:" + device); + } + } + + pw.println("\ngetCurrentHeadset() = " + device); + pw.println("getBatteryUsageHint() = " + + mBluetoothHeadset.getBatteryUsageHint(device)); + mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset); } - pw.println("\ngetCurrentHeadset() = " + headset.getCurrentHeadset()); - pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint()); - headset.close(); pw.println("\n--Application Service Records--"); for (Integer handle : mServiceRecordToPid.keySet()) { Integer pid = mServiceRecordToPid.get(handle); @@ -2504,6 +2531,16 @@ public class BluetoothService extends IBluetooth.Stub { } } + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothHeadset = (BluetoothHeadset) proxy; + } + public void onServiceDisconnected(int profile) { + mBluetoothHeadset = null; + } + }; + /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) { if (pairable && discoverable) return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; @@ -2563,6 +2600,8 @@ public class BluetoothService extends IBluetooth.Stub { } public boolean connectHeadset(String address) { + if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); if (state != null) { Message msg = new Message(); @@ -2575,6 +2614,8 @@ public class BluetoothService extends IBluetooth.Stub { } public boolean disconnectHeadset(String address) { + if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); if (state != null) { Message msg = new Message(); @@ -2587,6 +2628,8 @@ public class BluetoothService extends IBluetooth.Stub { } public boolean connectSink(String address) { + if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); if (state != null) { Message msg = new Message(); @@ -2599,6 +2642,8 @@ public class BluetoothService extends IBluetooth.Stub { } public boolean disconnectSink(String address) { + if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); if (state != null) { Message msg = new Message(); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 733b535..220e023 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -23,19 +23,12 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; -import android.widget.TextView.CursorController; // XXX this doesn't extend MetaKeyKeyListener because the signatures // don't match. Need to figure that out. Meanwhile the meta keys // won't work in fields that don't take input. public class ArrowKeyMovementMethod implements MovementMethod { - /** - * An optional controller for the cursor. - * Use {@link #setCursorController(CursorController)} to set this field. - */ - private CursorController mCursorController; - private boolean isCap(Spannable buffer) { return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) || (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0)); @@ -192,21 +185,10 @@ public class ArrowKeyMovementMethod implements MovementMethod { } public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { - if (mCursorController != null) { - mCursorController.hide(); - } return false; } public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - if (mCursorController != null) { - return onTouchEventCursor(widget, buffer, event); - } else { - return onTouchEventStandard(widget, buffer, event); - } - } - - private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) { int initialScrollX = -1, initialScrollY = -1; if (event.getAction() == MotionEvent.ACTION_UP) { initialScrollX = Touch.getInitialScrollX(widget, buffer); @@ -278,49 +260,6 @@ public class ArrowKeyMovementMethod implements MovementMethod { return handled; } - private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) { - if (widget.isFocused() && !widget.didTouchFocusSelect()) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_MOVE: - widget.cancelLongPress(); - - // Offset the current touch position (from controller to cursor) - final float x = event.getX() + mCursorController.getOffsetX(); - final float y = event.getY() + mCursorController.getOffsetY(); - mCursorController.updatePosition((int) x, (int) y); - return true; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mCursorController = null; - return true; - } - } - return false; - } - - /** - * Defines the cursor controller. - * - * When set, this object can be used to handle touch events, that can be translated into cursor - * updates. - * - * {@link MotionEvent#ACTION_MOVE} events will call back the - * {@link CursorController#updatePosition(int, int)} controller's method, passing the current - * finger coordinates (offset by {@link CursorController#getOffsetX()} and - * {@link CursorController#getOffsetY()}) as parameters. - * - * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or - * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null. - * - * @param cursorController A cursor controller implementation - * - * @hide - */ - public void setCursorController(CursorController cursorController) { - mCursorController = cursorController; - } - public boolean canSelectArbitrarily() { return true; } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c147b74..dc3b44d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -590,6 +590,19 @@ public interface WindowManager extends ViewManager { * also been set. */ public static final int FLAG_DISMISS_KEYGUARD = 0x00400000; + + /** Window flag: when set the window will accept for touch events + * outside of its bounds to be sent to other windows that also + * support split touch. When this flag is not set, the first pointer + * that goes down determines the window to which all subsequent touches + * go until all pointers go up. When this flag is set, each pointer + * (not necessarily the first) that goes down determines the window + * to which all subsequent touches of that pointer will go until that + * pointer goes up thereby enabling touches with multiple pointers + * to be split across multiple windows. + * + * {@hide} */ + public static final int FLAG_SPLIT_TOUCH = 0x00800000; /** Window flag: *sigh* The lock screen wants to continue running its * animation while it is fading. A kind-of hack to allow this. Maybe diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 35cfbcb..efe4b9d 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -74,7 +74,8 @@ class BrowserFrame extends Handler { // queue has been cleared,they are ignored. private boolean mBlockMessages = false; - private static String sDataDirectory = ""; + private static String sDatabaseDirectory; + private static String sCacheDirectory; // Is this frame the main frame? private boolean mIsMainFrame; @@ -228,9 +229,11 @@ class BrowserFrame extends Handler { AssetManager am = context.getAssets(); nativeCreateFrame(w, am, proxy.getBackForwardList()); - if (sDataDirectory.length() == 0) { - String dir = appContext.getFilesDir().getAbsolutePath(); - sDataDirectory = dir.substring(0, dir.lastIndexOf('/')); + if (sDatabaseDirectory == null) { + sDatabaseDirectory = appContext.getDatabasePath("dummy").getParent(); + } + if (sCacheDirectory == null) { + sCacheDirectory = appContext.getCacheDir().getAbsolutePath(); } if (DebugFlags.BROWSER_FRAME) { @@ -652,11 +655,19 @@ class BrowserFrame extends Handler { } /** - * Called by JNI. Gets the applications data directory - * @return String The applications data directory + * Called by JNI. Gets the application's database directory, excluding the trailing slash. + * @return String The application's database directory + */ + private static String getDatabaseDirectory() { + return sDatabaseDirectory; + } + + /** + * Called by JNI. Gets the application's cache directory, excluding the trailing slash. + * @return String The application's cache directory */ - private static String getDataDirectory() { - return sDataDirectory; + private static String getCacheDirectory() { + return sCacheDirectory; } /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index bca9b36..7944807 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -457,6 +457,9 @@ public class WebView extends AbsoluteLayout // default is not set, the UI will continue handle them. private boolean mDeferTouchProcess; + // if true, multi-touch events will be passed to webkit directly before UI + private boolean mDeferMultitouch = false; + // to avoid interfering with the current touch events, track them // separately. Currently no snapping or fling in the deferred process mode private int mDeferTouchMode = TOUCH_DONE_MODE; @@ -604,7 +607,7 @@ public class WebView extends AbsoluteLayout static final int HIDE_FULLSCREEN = 121; static final int DOM_FOCUS_CHANGED = 122; static final int REPLACE_BASE_CONTENT = 123; - // 124; + static final int FORM_DID_BLUR = 124; static final int RETURN_LABEL = 125; static final int FIND_AGAIN = 126; static final int CENTER_FIT_RECT = 127; @@ -656,7 +659,7 @@ public class WebView extends AbsoluteLayout "HIDE_FULLSCREEN", // = 121; "DOM_FOCUS_CHANGED", // = 122; "REPLACE_BASE_CONTENT", // = 123; - "124", // = 124; + "FORM_DID_BLUR", // = 124; "RETURN_LABEL", // = 125; "FIND_AGAIN", // = 126; "CENTER_FIT_RECT", // = 127; @@ -1872,10 +1875,18 @@ public class WebView extends AbsoluteLayout * @hide pending API council approval. */ public static boolean cleanupPrivateBrowsingFiles(Context context) { - return nativeCleanupPrivateBrowsingFiles(context.getFilesDir().getParent()); + // It seems wrong that we have to pass the storage locations here, given + // that the storage files are created native-side in WebRequestContext + // (albeit using a dumb getter on BrowserFrame to get the paths from + // Java). It looks like this is required because we may need to call + // this method before the BrowserFrame has been set up. + // TODO: Investigate whether this can be avoided. + return nativeCleanupPrivateBrowsingFiles(context.getDatabasePath("dummy").getParent(), + context.getCacheDir().getAbsolutePath()); } - private static native boolean nativeCleanupPrivateBrowsingFiles(String dataDirectory); + private static native boolean nativeCleanupPrivateBrowsingFiles(String databaseDirectory, + String cacheDirectory); private boolean extendScroll(int y) { int finalY = mScroller.getFinalY(); @@ -3843,10 +3854,9 @@ public class WebView extends AbsoluteLayout // Called by WebKit to instruct the UI to hide the keyboard private void hideSoftKeyboard() { - InputMethodManager imm = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm.isActive(this) - || (inEditingMode() && imm.isActive(mWebTextView))) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && (imm.isActive(this) + || (inEditingMode() && imm.isActive(mWebTextView)))) { imm.hideSoftInputFromWindow(this.getWindowToken(), 0); } } @@ -4938,13 +4948,14 @@ public class WebView extends AbsoluteLayout @Override public boolean onTouchEvent(MotionEvent ev) { - if (mNativeClass == 0 || !isClickable() || !isLongClickable()) { + if (mNativeClass == 0 || (!isClickable() && !isLongClickable())) { return false; } if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" - + mTouchMode); + Log.v(LOGTAG, ev + " at " + ev.getEventTime() + + " mTouchMode=" + mTouchMode + + " numPointers=" + ev.getPointerCount()); } int action = ev.getAction(); @@ -4991,16 +5002,18 @@ public class WebView extends AbsoluteLayout } } - // FIXME: we may consider to give WebKit an option to handle multi-touch - // events later. - if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1 && - mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) { - - // if the page disallows zoom, skip multi-pointer action - if (!mZoomManager.supportsPanDuringZoom() && mZoomManager.isZoomScaleFixed()) { - return true; + // If the page disallows zoom, pass multi-pointer events to webkit. + if (ev.getPointerCount() > 1 + && (mZoomManager.isZoomScaleFixed() || mDeferMultitouch)) { + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "passing " + ev.getPointerCount() + " points to webkit"); } + passMultiTouchToWebKit(ev); + return true; + } + if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1 && + mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) { if (!detector.isInProgress() && ev.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN) { // Insert a fake pointer down event in order to start @@ -5151,8 +5164,8 @@ public class WebView extends AbsoluteLayout if (shouldForwardTouchEvent()) { TouchEventData ted = new TouchEventData(); ted.mAction = action; - ted.mX = contentX; - ted.mY = contentY; + ted.mPoints = new Point[1]; + ted.mPoints[0] = new Point(contentX, contentY); ted.mMetaState = ev.getMetaState(); ted.mReprocess = mDeferTouchProcess; mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); @@ -5193,8 +5206,8 @@ public class WebView extends AbsoluteLayout || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) { TouchEventData ted = new TouchEventData(); ted.mAction = action; - ted.mX = contentX; - ted.mY = contentY; + ted.mPoints = new Point[1]; + ted.mPoints[0] = new Point(contentX, contentY); ted.mMetaState = ev.getMetaState(); ted.mReprocess = mDeferTouchProcess; mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); @@ -5382,8 +5395,8 @@ public class WebView extends AbsoluteLayout if (shouldForwardTouchEvent()) { TouchEventData ted = new TouchEventData(); ted.mAction = action; - ted.mX = contentX; - ted.mY = contentY; + ted.mPoints = new Point[1]; + ted.mPoints[0] = new Point(contentX, contentY); ted.mMetaState = ev.getMetaState(); ted.mReprocess = mDeferTouchProcess; mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); @@ -5396,8 +5409,8 @@ public class WebView extends AbsoluteLayout if (inFullScreenMode() || mDeferTouchProcess) { TouchEventData ted = new TouchEventData(); ted.mAction = WebViewCore.ACTION_DOUBLETAP; - ted.mX = contentX; - ted.mY = contentY; + ted.mPoints = new Point[1]; + ted.mPoints[0] = new Point(contentX, contentY); ted.mMetaState = ev.getMetaState(); ted.mReprocess = mDeferTouchProcess; mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); @@ -5513,14 +5526,32 @@ public class WebView extends AbsoluteLayout return true; } + private void passMultiTouchToWebKit(MotionEvent ev) { + TouchEventData ted = new TouchEventData(); + ted.mAction = ev.getAction() & MotionEvent.ACTION_MASK; + final int count = ev.getPointerCount(); + ted.mPoints = new Point[count]; + for (int c = 0; c < count; c++) { + int x = viewToContentX((int) ev.getX(c) + mScrollX); + int y = viewToContentY((int) ev.getY(c) + mScrollY); + ted.mPoints[c] = new Point(x, y); + } + ted.mMetaState = ev.getMetaState(); + ted.mReprocess = mDeferTouchProcess; + mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); + cancelLongPress(); + mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); + mPreventDefault = PREVENT_DEFAULT_IGNORE; + } + private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) { if (shouldForwardTouchEvent()) { if (removeEvents) { mWebViewCore.removeMessages(EventHub.TOUCH_EVENT); } TouchEventData ted = new TouchEventData(); - ted.mX = x; - ted.mY = y; + ted.mPoints = new Point[1]; + ted.mPoints[0] = new Point(x, y); ted.mAction = MotionEvent.ACTION_CANCEL; mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); mPreventDefault = PREVENT_DEFAULT_IGNORE; @@ -6517,8 +6548,9 @@ public class WebView extends AbsoluteLayout if (inFullScreenMode() || mDeferTouchProcess) { TouchEventData ted = new TouchEventData(); ted.mAction = WebViewCore.ACTION_LONGPRESS; - ted.mX = viewToContentX((int) mLastTouchX + mScrollX); - ted.mY = viewToContentY((int) mLastTouchY + mScrollY); + ted.mPoints = new Point[1]; + ted.mPoints[0] = new Point(viewToContentX((int) mLastTouchX + mScrollX), + viewToContentY((int) mLastTouchY + mScrollY)); // metaState for long press is tricky. Should it be the // state when the press started or when the press was // released? Or some intermediary key state? For @@ -6655,6 +6687,12 @@ public class WebView extends AbsoluteLayout updateTextSelectionFromMessage(msg.arg1, msg.arg2, (WebViewCore.TextSelectionData) msg.obj); break; + case FORM_DID_BLUR: + if (inEditingMode() + && mWebTextView.isSameTextField(msg.arg1)) { + hideSoftKeyboard(); + } + break; case RETURN_LABEL: if (inEditingMode() && mWebTextView.isSameTextField(msg.arg1)) { @@ -6746,16 +6784,16 @@ public class WebView extends AbsoluteLayout TouchEventData ted = (TouchEventData) msg.obj; switch (ted.mAction) { case MotionEvent.ACTION_DOWN: - mLastDeferTouchX = contentToViewX(ted.mX) + mLastDeferTouchX = contentToViewX(ted.mPoints[0].x) - mScrollX; - mLastDeferTouchY = contentToViewY(ted.mY) + mLastDeferTouchY = contentToViewY(ted.mPoints[0].y) - mScrollY; mDeferTouchMode = TOUCH_INIT_MODE; break; case MotionEvent.ACTION_MOVE: { // no snapping in defer process - int x = contentToViewX(ted.mX) - mScrollX; - int y = contentToViewY(ted.mY) - mScrollY; + int x = contentToViewX(ted.mPoints[0].x) - mScrollX; + int y = contentToViewY(ted.mPoints[0].y) - mScrollY; if (mDeferTouchMode != TOUCH_DRAG_MODE) { mDeferTouchMode = TOUCH_DRAG_MODE; mLastDeferTouchX = x; @@ -6784,8 +6822,8 @@ public class WebView extends AbsoluteLayout break; case WebViewCore.ACTION_DOUBLETAP: // doDoubleTap() needs mLastTouchX/Y as anchor - mLastTouchX = contentToViewX(ted.mX) - mScrollX; - mLastTouchY = contentToViewY(ted.mY) - mScrollY; + mLastTouchX = contentToViewX(ted.mPoints[0].x) - mScrollX; + mLastTouchY = contentToViewY(ted.mPoints[0].y) - mScrollY; mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY); mDeferTouchMode = TOUCH_DONE_MODE; break; @@ -7494,6 +7532,17 @@ public class WebView extends AbsoluteLayout } /** + * Toggle whether multi touch events should be sent to webkit + * no matter if UI wants to handle it first. + * + * @hide This is only used by the webkit layout test. + */ + public void setDeferMultiTouch(boolean value) { + mDeferMultitouch = value; + Log.v(LOGTAG, "set mDeferMultitouch to " + value); + } + + /** * Update our cache with updatedText. * @param updatedText The new text to put in our cache. */ diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 122cf6a..7462668 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -273,6 +273,16 @@ final class WebViewCore { mCallbackProxy.onJsAlert(url, message); } + /** + * Called by JNI. Send a message to the UI thread to hide the soft keyboard + * if the node pointed to by nodePointer is still in focus. + * @param nodePointer The node which just blurred. + */ + private void formDidBlur(int nodePointer) { + if (mWebView == null) return; + Message.obtain(mWebView.mPrivateHandler, WebView.FORM_DID_BLUR, + nodePointer, 0).sendToTarget(); + } /** * Called by JNI. Open a file chooser to upload a file. @@ -506,8 +516,8 @@ final class WebViewCore { private native void nativeTouchUp(int touchGeneration, int framePtr, int nodePtr, int x, int y); - private native boolean nativeHandleTouchEvent(int action, int x, int y, - int metaState); + private native boolean nativeHandleTouchEvent(int action, int[] x, int[] y, + int count, int metaState); private native void nativeUpdateFrameCache(); @@ -711,8 +721,7 @@ final class WebViewCore { static class TouchEventData { int mAction; - int mX; - int mY; + Point[] mPoints; int mMetaState; boolean mReprocess; } @@ -1180,12 +1189,19 @@ final class WebViewCore { case TOUCH_EVENT: { TouchEventData ted = (TouchEventData) msg.obj; + final int count = ted.mPoints.length; + int[] xArray = new int[count]; + int[] yArray = new int[count]; + for (int c = 0; c < count; c++) { + xArray[c] = ted.mPoints[c].x; + yArray[c] = ted.mPoints[c].y; + } Message.obtain( mWebView.mPrivateHandler, WebView.PREVENT_TOUCH_ID, ted.mAction, - nativeHandleTouchEvent(ted.mAction, ted.mX, - ted.mY, ted.mMetaState) ? 1 : 0, + nativeHandleTouchEvent(ted.mAction, xArray, + yArray, count, ted.mMetaState) ? 1 : 0, ted.mReprocess ? ted : null).sendToTarget(); break; } diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index d7b4452..8f89678 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -67,6 +67,9 @@ public class WebViewDatabase { private final Object mFormLock = new Object(); private final Object mHttpAuthLock = new Object(); + // TODO: The Chromium HTTP stack handles cookies independently. + // We should consider removing the cookies table if and when we switch to + // the Chromium HTTP stack for good. private static final String mTableNames[] = { "cookies", "password", "formurl", "formdata", "httpauth" }; diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 71f4f03..1bc0612 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -87,6 +87,7 @@ public class PopupWindow { private boolean mTouchable = true; private boolean mOutsideTouchable = false; private boolean mClippingEnabled = true; + private boolean mSplitTouchEnabled; private OnTouchListener mTouchInterceptor; @@ -576,6 +577,36 @@ public class PopupWindow { } /** + * <p>Indicates whether the popup window supports splitting touches.</p> + * + * @return true if the touch splitting is enabled, false otherwise + * + * @see #setSplitTouchEnabled(boolean) + * @hide + */ + public boolean isSplitTouchEnabled() { + return mSplitTouchEnabled; + } + + /** + * <p>Allows the popup window to split touches across other windows that also + * support split touch. When this flag is not set, the first pointer + * that goes down determines the window to which all subsequent touches + * go until all pointers go up. When this flag is set, each pointer + * (not necessarily the first) that goes down determines the window + * to which all subsequent touches of that pointer will go until that + * pointer goes up thereby enabling touches with multiple pointers + * to be split across multiple windows.</p> + * + * @param enabled true if the split touches should be enabled, false otherwise + * @see #isSplitTouchEnabled() + * @hide + */ + public void setSplitTouchEnabled(boolean enabled) { + mSplitTouchEnabled = enabled; + } + + /** * <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 @@ -900,6 +931,9 @@ public class PopupWindow { if (!mClippingEnabled) { curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; } + if (mSplitTouchEnabled) { + curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; + } return curFlags; } diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index afb56fc..27f5ad4 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -122,7 +122,7 @@ public class RemoteViewsAdapter extends BaseAdapter { // Post a runnable to call back to the view to notify it that we have // connected - adapter. mMainQueue.post(new Runnable() { + adapter.mMainQueue.post(new Runnable() { @Override public void run() { final RemoteAdapterConnectionCallback callback = @@ -148,7 +148,7 @@ public class RemoteViewsAdapter extends BaseAdapter { adapter.mMainQueue.removeMessages(0); adapter.mWorkerQueue.removeMessages(0); - // Clear the cache + // Clear the cache (the meta data will be re-requested on service re-connection) synchronized (adapter.mCache) { adapter.mCache.reset(); } @@ -183,9 +183,13 @@ public class RemoteViewsAdapter extends BaseAdapter { * successfully. */ public void onRemoteViewsLoaded(RemoteViews view) { - // Remove all the children of this layout first - removeAllViews(); - addView(view.apply(getContext(), this)); + try { + // Remove all the children of this layout first + removeAllViews(); + addView(view.apply(getContext(), this)); + } catch (Exception e) { + Log.e(TAG, "Failed to apply RemoteViews."); + } } } @@ -224,6 +228,8 @@ public class RemoteViewsAdapter extends BaseAdapter { * the associated RemoteViews has loaded. */ public void notifyOnRemoteViewsLoaded(int position, RemoteViews view, int typeId) { + if (view == null) return; + final Integer pos = position; if (mReferences.containsKey(pos)) { // Notify all the references for that position of the newly loaded RemoteViews @@ -555,11 +561,14 @@ public class RemoteViewsAdapter extends BaseAdapter { } public void reset() { + // Note: We do not try and reset the meta data, since that information is still used by + // collection views to validate it's own contents (and will be re-requested if the data + // is invalidated through the notifyDataSetChanged() flow). + mPreloadLowerBound = 0; mPreloadUpperBound = -1; mIndexRemoteViews.clear(); mIndexMetaData.clear(); - mMetaData.reset(); synchronized (mLoadIndices) { mRequestedIndices.clear(); mLoadIndices.clear(); @@ -834,11 +843,6 @@ public class RemoteViewsAdapter extends BaseAdapter { } public void notifyDataSetChanged() { - synchronized (mCache) { - // Flush the cache so that we can reload new items from the service - mCache.reset(); - } - final RemoteViewsMetaData metaData = mCache.getMetaData(); synchronized (metaData) { // Set flag to calls the remote factory's onDataSetChanged() on the next worker loop @@ -864,6 +868,11 @@ public class RemoteViewsAdapter extends BaseAdapter { } } + // Flush the cache so that we can reload new items from the service + synchronized (mCache) { + mCache.reset(); + } + // Re-request the new metadata (only after the notification to the factory) updateMetaData(); diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index b9ded190..16126aa 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -82,27 +82,27 @@ public abstract class RemoteViewsService extends Service { public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) { mFactory = factory; } - public void onDataSetChanged() { + public synchronized void onDataSetChanged() { mFactory.onDataSetChanged(); } - public int getCount() { + public synchronized int getCount() { return mFactory.getCount(); } - public RemoteViews getViewAt(int position) { + public synchronized RemoteViews getViewAt(int position) { RemoteViews rv = mFactory.getViewAt(position); rv.setIsWidgetCollectionChild(true); return rv; } - public RemoteViews getLoadingView() { + public synchronized RemoteViews getLoadingView() { return mFactory.getLoadingView(); } - public int getViewTypeCount() { + public synchronized int getViewTypeCount() { return mFactory.getViewTypeCount(); } - public long getItemId(int position) { + public synchronized long getItemId(int position) { return mFactory.getItemId(position); } - public boolean hasStableIds() { + public synchronized boolean hasStableIds() { return mFactory.hasStableIds(); } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 09217af..dd67197 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.database.Cursor; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.Editable; @@ -348,15 +349,19 @@ public class SearchView extends LinearLayout { } private void setImeVisibility(boolean visible) { - // We made sure the IME was displayed, so also make sure it is closed - // when we go away. - InputMethodManager imm = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) { - if (visible) { - imm.showSoftInputUnchecked(0, null); - } else { - imm.hideSoftInputFromWindow(getWindowToken(), 0); + // don't mess with the soft input if we're not iconified by default + if (mIconifiedByDefault) { + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + // We made sure the IME was displayed, so also make sure it is closed + // when we go away. + if (imm != null) { + if (visible) { + imm.showSoftInputUnchecked(0, null); + } else { + imm.hideSoftInputFromWindow(getWindowToken(), 0); + } } } } @@ -717,4 +722,13 @@ public class SearchView extends LinearLayout { public void afterTextChanged(Editable s) { } }; -} + + /* + * Avoid getting focus when searching for something to focus on. + * The user will have to touch the text view to get focus. + */ + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + return false; + } + } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d49b4d7..f4d193f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -7688,16 +7688,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * A CursorController instance can be used to control a cursor in the text. - * - * It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events - * and send them to this object instead of the cursor. - * + * It is not used outside of {@link TextView}. * @hide */ - public interface CursorController { - /* Cursor fade-out animation duration, in milliseconds. */ - static final int FADE_OUT_DURATION = 400; - + private interface CursorController { /** * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}. * See also {@link #hide()}. @@ -7718,23 +7712,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Update the controller's position. */ - public void updatePosition(int x, int y); + public void updatePosition(HandleView handle, int x, int y); public void updatePosition(); /** - * The controller and the cursor's positions can be link by a fixed offset, - * computed when the controller is touched, and then maintained as it moves - * @return Horizontal offset between the controller and the cursor. - */ - public float getOffsetX(); - - /** - * @return Vertical offset between the controller and the cursor. - */ - public float getOffsetY(); - - /** * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller * a chance to become active and/or visible. * @param event The touch event @@ -7757,6 +7739,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mDrawable = handle; mContainer = new PopupWindow(TextView.this.mContext, null, com.android.internal.R.attr.textSelectHandleWindowStyle); + mContainer.setSplitTouchEnabled(true); } @Override @@ -7855,7 +7838,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener TextView.this.getLocationOnScreen(coords); final int x = (int) (rawX - coords[0] + 0.5f); final int y = (int) (rawY - coords[1] + 0.5f); - mController.updatePosition(x, y); + mController.updatePosition(this, x, y); break; case MotionEvent.ACTION_UP: @@ -7889,13 +7872,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - class InsertionPointCursorController implements CursorController { + private class InsertionPointCursorController implements CursorController { private static final int DELAY_BEFORE_FADE_OUT = 4100; // The cursor controller image private final HandleView mHandle; - // Offset between finger hot point on cursor controller and actual cursor - private float mOffsetX, mOffsetY; private final Runnable mHider = new Runnable() { public void run() { @@ -7928,7 +7909,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mHandle.isShowing(); } - public void updatePosition(int x, int y) { + public void updatePosition(HandleView handle, int x, int y) { final int previousOffset = getSelectionStart(); int offset = getHysteresisOffset(x, y, previousOffset); @@ -7952,24 +7933,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mHandle.positionAtCursor(offset, true); } - public float getOffsetX() { - return mOffsetX; - } - - public float getOffsetY() { - return mOffsetY; - } - public boolean onTouchEvent(MotionEvent ev) { return false; } } - class SelectionModifierCursorController implements CursorController { + private class SelectionModifierCursorController implements CursorController { // The cursor controller images private HandleView mStartHandle, mEndHandle; - // Offset between finger hot point on active cursor controller and actual cursor - private float mOffsetX, mOffsetY; // The offsets of that last touch down event. Remembered to start selection there. private int mMinTouchOffset, mMaxTouchOffset; // Whether selection anchors are active @@ -8003,15 +7974,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hide(); } - public void updatePosition(int x, int y) { + public void updatePosition(HandleView handle, int x, int y) { int selectionStart = getSelectionStart(); int selectionEnd = getSelectionEnd(); - final int previousOffset = mStartHandle.isDragging() ? selectionStart : selectionEnd; + final int previousOffset = handle == mStartHandle ? selectionStart : selectionEnd; int offset = getHysteresisOffset(x, y, previousOffset); // Handle the case where start and end are swapped, making sure start <= end - if (mStartHandle.isDragging()) { + if (handle == mStartHandle) { if (offset <= selectionEnd) { if (selectionStart == offset) { return; // no change, no need to redraw; @@ -8108,14 +8079,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mMaxTouchOffset; } - public float getOffsetX() { - return mOffsetX; - } - - public float getOffsetY() { - return mOffsetY; - } - /** * @return true iff this controller is currently used to move the selection start. */ diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 66149ac..8ea02aa 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -18,6 +18,7 @@ package com.android.internal.os; import com.android.internal.util.JournaledFile; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.net.TrafficStats; import android.os.BatteryManager; @@ -50,6 +51,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; @@ -1150,7 +1152,11 @@ public final class BatteryStatsImpl extends BatteryStats { private int getCurrentBluetoothPingCount() { if (mBtHeadset != null) { - return mBtHeadset.getBatteryUsageHint(); + Set<BluetoothDevice> deviceSet = mBtHeadset.getConnectedDevices(); + BluetoothDevice[] devices = deviceSet.toArray(new BluetoothDevice[deviceSet.size()]); + if (devices.length > 0) { + return mBtHeadset.getBatteryUsageHint(devices[0]); + } } return -1; } diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java index aa340fb..2b96bf6 100644 --- a/core/java/com/android/internal/statusbar/StatusBarNotification.java +++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java @@ -121,6 +121,10 @@ public class StatusBarNotification implements Parcelable { return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; } + public boolean isClearable() { + return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) + && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0); + } } diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 8311c80..d517d4c 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -84,7 +84,7 @@ public class MenuBuilder implements Menu { static final int THEME_RES_FOR_TYPE[] = new int[] { com.android.internal.R.style.Theme_IconMenu, com.android.internal.R.style.Theme_ExpandedMenu, - 0, + com.android.internal.R.style.Theme_Light, -1, -1, }; diff --git a/core/java/com/trustedlogic/trustednfc/android/ILlcpConnectionlessSocket.aidl b/core/java/com/trustedlogic/trustednfc/android/ILlcpConnectionlessSocket.aidl new file mode 100644 index 0000000..35746ad --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/ILlcpConnectionlessSocket.aidl @@ -0,0 +1,34 @@ +/* + * 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.trustedlogic.trustednfc.android; + +import com.trustedlogic.trustednfc.android.LlcpPacket; + +/** + * TODO + * + * {@hide} + */ +interface ILlcpConnectionlessSocket +{ + + void close(int nativeHandle); + int getSap(int nativeHandle); + LlcpPacket receiveFrom(int nativeHandle); + int sendTo(int nativeHandle, in LlcpPacket packet); + +}
\ No newline at end of file diff --git a/core/java/com/trustedlogic/trustednfc/android/ILlcpServiceSocket.aidl b/core/java/com/trustedlogic/trustednfc/android/ILlcpServiceSocket.aidl new file mode 100644 index 0000000..5eb1f3c --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/ILlcpServiceSocket.aidl @@ -0,0 +1,32 @@ +/* + * 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.trustedlogic.trustednfc.android; + +/** + * TODO + * + * {@hide} + */ +interface ILlcpServiceSocket +{ + + int accept(int nativeHandle); + void close(int nativeHandle); + int getAcceptTimeout(int nativeHandle); + void setAcceptTimeout(int nativeHandle, int timeout); + +}
\ No newline at end of file diff --git a/core/java/com/trustedlogic/trustednfc/android/ILlcpSocket.aidl b/core/java/com/trustedlogic/trustednfc/android/ILlcpSocket.aidl new file mode 100644 index 0000000..e9169d8 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/ILlcpSocket.aidl @@ -0,0 +1,41 @@ +/* + * 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.trustedlogic.trustednfc.android; + +/** + * TODO + * + * {@hide} + */ +interface ILlcpSocket +{ + + int close(int nativeHandle); + int connect(int nativeHandle, int sap); + int connectByName(int nativeHandle, String sn); + int getConnectTimeout(int nativeHandle); + int getLocalSap(int nativeHandle); + int getLocalSocketMiu(int nativeHandle); + int getLocalSocketRw(int nativeHandle); + int getRemoteSocketMiu(int nativeHandle); + int getRemoteSocketRw(int nativeHandle); + int receive(int nativeHandle, out byte[] receiveBuffer); + int send(int nativeHandle, in byte[] data); + void setConnectTimeout(int nativeHandle, int timeout); + +} + diff --git a/core/java/com/trustedlogic/trustednfc/android/INdefTag.aidl b/core/java/com/trustedlogic/trustednfc/android/INdefTag.aidl new file mode 100644 index 0000000..1f8d1a4 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/INdefTag.aidl @@ -0,0 +1,32 @@ +/* + * 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.trustedlogic.trustednfc.android; + +import com.trustedlogic.trustednfc.android.NdefMessage; + +/** + * TODO + * + * {@hide} + */ +interface INdefTag +{ + + NdefMessage read(int nativeHandle); + boolean write(int nativeHandle, in NdefMessage msg); + +}
\ No newline at end of file diff --git a/core/java/com/trustedlogic/trustednfc/android/INfcManager.aidl b/core/java/com/trustedlogic/trustednfc/android/INfcManager.aidl new file mode 100644 index 0000000..ce36ab2 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/INfcManager.aidl @@ -0,0 +1,61 @@ +/* + * 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.trustedlogic.trustednfc.android; + +import com.trustedlogic.trustednfc.android.ILlcpSocket; +import com.trustedlogic.trustednfc.android.ILlcpServiceSocket; +import com.trustedlogic.trustednfc.android.ILlcpConnectionlessSocket; +import com.trustedlogic.trustednfc.android.INfcTag; +import com.trustedlogic.trustednfc.android.IP2pTarget; +import com.trustedlogic.trustednfc.android.IP2pInitiator; + + +/** + * Interface that allows controlling NFC activity. + * + * {@hide} + */ +interface INfcManager +{ + + ILlcpSocket getLlcpInterface(); + ILlcpConnectionlessSocket getLlcpConnectionlessInterface(); + ILlcpServiceSocket getLlcpServiceInterface(); + INfcTag getNfcTagInterface(); + IP2pTarget getP2pTargetInterface(); + IP2pInitiator getP2pInitiatorInterface(); + + void cancel(); + int createLlcpConnectionlessSocket(int sap); + int createLlcpServiceSocket(int sap, String sn, int miu, int rw, int linearBufferLength); + int createLlcpSocket(int sap, int miu, int rw, int linearBufferLength); + int deselectSecureElement(); + boolean disable(); + boolean enable(); + int getOpenTimeout(); + String getProperties(String param); + int[] getSecureElementList(); + int getSelectedSecureElement(); + boolean isEnabled(); + int openP2pConnection(); + int openTagConnection(); + int selectSecureElement(int seId); + void setOpenTimeout(int timeout); + int setProperties(String param, String value); + +} + diff --git a/core/java/com/trustedlogic/trustednfc/android/INfcTag.aidl b/core/java/com/trustedlogic/trustednfc/android/INfcTag.aidl new file mode 100644 index 0000000..79543c4 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/INfcTag.aidl @@ -0,0 +1,38 @@ +/* + * 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.trustedlogic.trustednfc.android; + +import com.trustedlogic.trustednfc.android.NdefMessage; + +/** + * TODO + * + * {@hide} + */ +interface INfcTag +{ + + int close(int nativeHandle); + int connect(int nativeHandle); + String getType(int nativeHandle); + byte[] getUid(int nativeHandle); + boolean isNdef(int nativeHandle); + byte[] transceive(int nativeHandle, in byte[] data); + + NdefMessage read(int nativeHandle); + boolean write(int nativeHandle, in NdefMessage msg); +}
\ No newline at end of file diff --git a/core/java/com/trustedlogic/trustednfc/android/IP2pInitiator.aidl b/core/java/com/trustedlogic/trustednfc/android/IP2pInitiator.aidl new file mode 100644 index 0000000..96819ae --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/IP2pInitiator.aidl @@ -0,0 +1,32 @@ +/* + * 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.trustedlogic.trustednfc.android; + +/** + * TODO + * + * {@hide} + */ +interface IP2pInitiator +{ + + byte[] getGeneralBytes(int nativeHandle); + int getMode(int nativeHandle); + byte[] receive(int nativeHandle); + boolean send(int nativeHandle, in byte[] data); + +}
\ No newline at end of file diff --git a/core/java/com/trustedlogic/trustednfc/android/IP2pTarget.aidl b/core/java/com/trustedlogic/trustednfc/android/IP2pTarget.aidl new file mode 100644 index 0000000..8dcdf18 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/IP2pTarget.aidl @@ -0,0 +1,33 @@ +/* + * 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.trustedlogic.trustednfc.android; + +/** + * TODO + * + * {@hide} + */ +interface IP2pTarget +{ + + byte[] getGeneralBytes(int nativeHandle); + int getMode(int nativeHandle); + int connect(int nativeHandle); + boolean disconnect(int nativeHandle); + byte[] transceive(int nativeHandle, in byte[] data); + +}
\ No newline at end of file diff --git a/core/java/com/trustedlogic/trustednfc/android/LlcpConnectionlessSocket.java b/core/java/com/trustedlogic/trustednfc/android/LlcpConnectionlessSocket.java new file mode 100644 index 0000000..0270626 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/LlcpConnectionlessSocket.java @@ -0,0 +1,148 @@ +/* + * 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. + */ + +/** + * File : LlcpConnectionLessSocket.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +import android.os.RemoteException; +import android.util.Log; + +/** + * LlcpConnectionlessSocket represents a LLCP Connectionless object to be used + * in a connectionless communication + * + * @since AA02.01 + * @hide + */ +public class LlcpConnectionlessSocket { + + + private static final String TAG = "LlcpConnectionlessSocket"; + + /** + * The handle returned by the NFC service and used to identify the LLCP connectionless socket in + * every call of this class. + * + * @hide + */ + protected int mHandle; + + + /** + * The entry point for LLCP Connectionless socket operations. + * + * @hide + */ + protected ILlcpConnectionlessSocket mService; + + + /** + * Internal constructor for the LlcpConnectionlessSocket class. + * + * @param service The entry point to the Nfc Service for LLCP Connectionless socket class. + * @param handle The handle returned by the NFC service and used to identify + * the socket in subsequent calls. + * @hide + */ + LlcpConnectionlessSocket(ILlcpConnectionlessSocket service, int handle) { + this.mService = service; + this.mHandle = handle; + } + + /** + * Send data to a specific LLCP Connectionless client + * + * @param packet Service Access Point number related to a LLCP + * Connectionless client and a data buffer to send + * @throws IOException if the LLCP link has been lost or deactivated. + * @since AA02.01 + */ + public void sendTo(LlcpPacket packet) throws IOException { + try { + int result = mService.sendTo(mHandle, packet); + // Handle potential errors + if (ErrorCodes.isError(result)) { + throw new IOException(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in sendTo(): ", e); + } + } + + /** + * Receive data from a LLCP Connectionless client + * + * @return data data received from a specific LLCP Connectionless client + * @throws IOException if the LLCP link has been lost or deactivated. + * @see LlcpPacket + * @since AA02.01 + */ + public LlcpPacket receiveFrom() throws IOException { + try { + LlcpPacket packet = mService.receiveFrom(mHandle); + if (packet != null) { + return packet; + }else{ + // Handle potential errors + throw new IOException(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in receiveFrom(): ", e); + } + return null; + } + + /** + * Close the created Connectionless socket. + * + * @since AA02.01 + */ + public void close() { + try { + mService.close(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in close(): ", e); + } + } + + /** + * Returns the local Service Access Point number of the socket + * + * @return sap + * @since AA02.01 + */ + public int getSap() { + int sap = 0; + + try { + sap = mService.getSap(mHandle); + + } catch (RemoteException e) { + + e.printStackTrace(); + } + return sap; + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/LlcpException.java b/core/java/com/trustedlogic/trustednfc/android/LlcpException.java new file mode 100644 index 0000000..1e2e2da --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/LlcpException.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +/** + * File : LLCPException.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + * Created : 24-02-2010 + */ + +package com.trustedlogic.trustednfc.android; + +/** + * Generic exception thrown in case something unexpected happened during a + * LLCP communication. + * + * @since AA02.01 + * @hide + */ +public class LlcpException extends Exception { + /** + * Constructs a new LlcpException with the current stack trace and the + * specified detail message. + * + * @param s the detail message for this exception. + */ + public LlcpException(String s) { + super(s); + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/LlcpPacket.aidl b/core/java/com/trustedlogic/trustednfc/android/LlcpPacket.aidl new file mode 100644 index 0000000..297a1fe --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/LlcpPacket.aidl @@ -0,0 +1,19 @@ +/* + * 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.trustedlogic.trustednfc.android; + +parcelable LlcpPacket; diff --git a/core/java/com/trustedlogic/trustednfc/android/LlcpPacket.java b/core/java/com/trustedlogic/trustednfc/android/LlcpPacket.java new file mode 100644 index 0000000..af79023 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/LlcpPacket.java @@ -0,0 +1,119 @@ +/* + * 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. + */ + +/** + * File : LLCPPacket.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + * Created : 25-02-2010 + */ + +package com.trustedlogic.trustednfc.android; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a LLCP packet received in a LLCP Connectionless communication; + * + * @since AA02.01 + * @hide + */ +public class LlcpPacket implements Parcelable { + + private int mRemoteSap; + + private byte[] mDataBuffer; + + /** + * Creator class, needed when implementing from Parcelable + * {@hide} + */ + public static final Parcelable.Creator<LlcpPacket> CREATOR = new Parcelable.Creator<LlcpPacket>() { + public LlcpPacket createFromParcel(Parcel in) { + // Remote SAP + short sap = (short)in.readInt(); + + // Data Buffer + int dataLength = in.readInt(); + byte[] data = new byte[dataLength]; + in.readByteArray(data); + + return new LlcpPacket(sap, data); + } + + public LlcpPacket[] newArray(int size) { + return new LlcpPacket[size]; + } + }; + + + /** + * Creates a LlcpPacket to be sent to a remote Service Access Point number + * (SAP) + * + * @param sap Remote Service Access Point number + * @param data Data buffer + * @since AA02.01 + */ + public LlcpPacket(int sap, byte[] data) { + mRemoteSap = sap; + mDataBuffer = data; + } + + /** + * @hide + */ + public LlcpPacket() { + } + + /** + * Returns the remote Service Access Point number + * + * @return remoteSap + * @since AA02.01 + */ + public int getRemoteSap() { + return mRemoteSap; + } + + /** + * Returns the data buffer + * + * @return data + * @since AA02.01 + */ + public byte[] getDataBuffer() { + return mDataBuffer; + } + + /** + * (Parcelable) Describe the parcel + * {@hide} + */ + public int describeContents() { + return 0; + } + + /** + * (Parcelable) Convert current object to a Parcel + * {@hide} + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRemoteSap); + dest.writeInt(mDataBuffer.length); + dest.writeByteArray(mDataBuffer); + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/LlcpServiceSocket.java b/core/java/com/trustedlogic/trustednfc/android/LlcpServiceSocket.java new file mode 100644 index 0000000..a152ecb1 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/LlcpServiceSocket.java @@ -0,0 +1,180 @@ +/* + * 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. + */ + +/** + * File : LLCPServerSocket.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +import android.os.RemoteException; +import android.util.Log; + +/** + * LlcpServiceSocket represents a LLCP Service to be used in a + * Connection-oriented communication + * + * @since AA02.01 + * @hide + */ +public class LlcpServiceSocket { + + private static final String TAG = "LlcpServiceSocket"; + + /** + * The handle returned by the NFC service and used to identify the LLCP + * Service socket in every call of this class. + * + * @hide + */ + protected int mHandle; + + /** + * The entry point for LLCP Service socket operations. + * + * @hide + */ + protected ILlcpServiceSocket mService; + + private ILlcpSocket mLlcpSocketService; + + static LlcpException convertErrorToLlcpException(int errorCode) { + return convertErrorToLlcpException(errorCode, null); + } + + static LlcpException convertErrorToLlcpException(int errorCode, + String message) { + if (message == null) { + message = ""; + } else { + message = " (" + message + ")"; + } + + switch (errorCode) { + case ErrorCodes.ERROR_SOCKET_CREATION: + return new LlcpException( + "Error during the creation of an Llcp socket" + message); + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + return new LlcpException("Not enough ressources are available" + + message); + default: + return new LlcpException("Unkown error code " + errorCode + message); + } + } + + /** + * Internal constructor for the LlcpServiceSocket class. + * + * @param service + * The entry point to the Nfc Service for LlcpServiceSocket + * class. + * @param handle + * The handle returned by the NFC service and used to identify + * the socket in subsequent calls. + * @hide + */ + LlcpServiceSocket(ILlcpServiceSocket service, ILlcpSocket socketService, int handle) { + this.mService = service; + this.mHandle = handle; + this.mLlcpSocketService = socketService; + } + + /** + * Wait for incomming connection request from a LLCP client and accept this + * request + * + * @return socket object to be used to communicate with a LLCP client + * + * @throws IOException + * if the llcp link is lost or deactivated + * @throws LlcpException + * if not enough ressources are available + * + * @see LlcpSocket + * @since AA02.01 + */ + public LlcpSocket accept() throws IOException, LlcpException { + + try { + int handle = mService.accept(mHandle); + // Handle potential errors + if (ErrorCodes.isError(handle)) { + if (handle == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToLlcpException(handle); + } + } + + // Build the public LlcpSocket object + return new LlcpSocket(mLlcpSocketService, handle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in accept(): ", e); + return null; + } + + } + + /** + * Set the timeout for the accept request + * + * @param timeout + * value of the timeout for the accept request + * @since AA02.01 + */ + public void setAcceptTimeout(int timeout) { + try { + mService.setAcceptTimeout(mHandle, timeout); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in setAcceptTimeout(): ", e); + } + } + + /** + * Get the timeout value of the accept request + * + * @return mTimeout + * @since AA02.01 + */ + public int getAcceptTimeout() { + try { + return mService.getAcceptTimeout(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getAcceptTimeout(): ", e); + return 0; + } + } + + /** + * Close the created Llcp Service socket + * + * @since AA02.01 + */ + public void close() { + try { + mService.close(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in close(): ", e); + } + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/LlcpSocket.java b/core/java/com/trustedlogic/trustednfc/android/LlcpSocket.java new file mode 100644 index 0000000..e47160c --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/LlcpSocket.java @@ -0,0 +1,344 @@ +/* + * 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. + */ + +/** + * File : LlcpClientSocket.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +import android.os.RemoteException; +import android.util.Log; + +/** + * LlcpClientSocket represents a LLCP Connection-Oriented client to be used in a + * connection-oriented communication + * + * @since AA02.01 + * @hide + */ +public class LlcpSocket { + + private static final String TAG = "LlcpSocket"; + + /** + * The handle returned by the NFC service and used to identify the LLCP + * socket in every call of this class. + * + * @hide + */ + protected int mHandle; + + /** + * The entry point for LLCP socket operations. + * + * @hide + */ + protected ILlcpSocket mService; + + static LlcpException convertErrorToLlcpException(int errorCode) { + return convertErrorToLlcpException(errorCode, null); + } + + static LlcpException convertErrorToLlcpException(int errorCode, + String message) { + if (message == null) { + message = ""; + } else { + message = " (" + message + ")"; + } + + switch (errorCode) { + case ErrorCodes.ERROR_SOCKET_CREATION: + return new LlcpException( + "Error during the creation of an Llcp socket" + message); + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + return new LlcpException("Not enough ressources are available" + + message); + case ErrorCodes.ERROR_SOCKET_NOT_CONNECTED: + return new LlcpException("Socket not connected to an Llcp Service" + + message); + default: + return new LlcpException("Unkown error code " + errorCode + message); + } + } + + /** + * Internal constructor for the LlcpSocket class. + * + * @param service + * The entry point to the Nfc Service for LlcpServiceSocket + * class. + * @param handle + * The handle returned by the NFC service and used to identify + * the socket in subsequent calls. + * @hide + */ + LlcpSocket(ILlcpSocket service, int handle) { + this.mService = service; + this.mHandle = handle; + } + + /** + * Connect request to a specific LLCP Service by its SAP. + * + * @param sap + * Service Access Point number of the LLCP Service + * @throws IOException + * if the LLCP has been lost or deactivated. + * @throws LlcpException + * if the connection request is rejected by the remote LLCP + * Service + * @since AA02.01 + */ + public void connect(int sap) throws IOException, LlcpException { + try { + int result = mService.connect(mHandle, sap); + // Handle potential errors + if (ErrorCodes.isError(result)) { + if (result == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToLlcpException(result); + } + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in accept(): ", e); + } + } + + /** + * Connect request to a specific LLCP Service by its Service Name. + * + * @param sn + * Service Name of the LLCP Service + * @throws IOException + * if the LLCP has been lost or deactivated. + * @throws LlcpException + * if the connection request is rejected by the remote LLCP + * Service + * @since AA02.01 + */ + public void connect(String sn) throws IOException, LlcpException { + try { + int result = mService.connectByName(mHandle, sn); + // Handle potential errors + if (ErrorCodes.isError(result)) { + if (result == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToLlcpException(result); + } + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in accept(): ", e); + } + } + + /** + * Set the timeout for the connect request + * + * @param timeout + * timeout value for the connect request + * @since AA02.01 + */ + public void setConnectTimeout(int timeout) { + try { + mService.setConnectTimeout(mHandle, timeout); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in setConnectTimeout(): ", e); + } + } + + /** + * Get the timeout value of the connect request + * + * @return mTimeout + * @since AA02.01 + */ + public int getConnectTimeout() { + try { + return mService.getConnectTimeout(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getConnectTimeout(): ", e); + return 0; + } + } + + /** + * Disconnect request to the connected LLCP socket and close the created + * socket. + * + * @throws IOException + * if the LLCP has been lost or deactivated. + * @since AA02.01 + */ + public void close() throws IOException { + try { + int result = mService.close(mHandle); + // Handle potential errors + if (ErrorCodes.isError(result)) { + throw new IOException(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in close(): ", e); + } + } + + /** + * Send data to the connected LLCP Socket. + * + * @throws IOException + * if the LLCP has been lost or deactivated. + * @since AA02.01 + */ + public void send(byte[] data) throws IOException { + try { + int result = mService.send(mHandle, data); + // Handle potential errors + if (ErrorCodes.isError(result)) { + throw new IOException(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in send(): ", e); + } + } + + /** + * Receive data from the connected LLCP socket + * + * @param receiveBuffer + * a buffer for the received data + * @return length length of the data received + * @throws IOException + * if the LLCP has been lost or deactivated. + * @since AA02.01 + */ + public int receive(byte[] receiveBuffer) throws IOException { + int receivedLength = 0; + try { + receivedLength = mService.receive(mHandle, receiveBuffer); + if(receivedLength == 0){ + throw new IOException(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in send(): ", e); + } + + return receivedLength; + } + + /** + * Returns the local Service Access Point number of the socket + * + * @return localSap + * @since AA02.01 + */ + public int getLocalSap() { + try { + return mService.getLocalSap(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getLocalSap(): ", e); + return 0; + } + } + + /** + * Returns the local Maximum Information Unit(MIU) of the socket + * + * @return miu + * @since AA02.01 + */ + public int getLocalSocketMiu() { + try { + return mService.getLocalSocketMiu(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getLocalSocketMiu(): ", e); + return 0; + } + } + + /** + * Returns the local Receive Window(RW) of the socket + * + * @return rw + * @since AA02.01 + */ + public int getLocalSocketRw() { + try { + return mService.getLocalSocketRw(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getLocalSocketRw(): ", e); + return 0; + } + } + + /** + * Returns the remote Maximum Information Unit(MIU) of the socket. + * <p> + * This method must be called when the socket is in CONNECTED_STATE + * + * @return remoteMiu + * @throws LlcpException + * if the LlcpClientSocket is not in a CONNECTED_STATE + * @since AA02.01 + */ + public int getRemoteSocketMiu() throws LlcpException { + try { + int result = mService.getRemoteSocketMiu(mHandle); + if(result != ErrorCodes.ERROR_SOCKET_NOT_CONNECTED){ + return result; + }else{ + throw convertErrorToLlcpException(result); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getRemoteSocketMiu(): ", e); + return 0; + } + } + + /** + * Returns the remote Receive Window(RW) of the connected remote socket. + * <p> + * This method must be called when the socket is in CONNECTED_STATE + * + * @return rw + * @throws LlcpException + * if the LlcpClientSocket is not in a CONNECTED_STATE + * @since AA02.01 + */ + public int getRemoteSocketRw() throws LlcpException { + try { + int result = mService.getRemoteSocketRw(mHandle); + if( result != ErrorCodes.ERROR_SOCKET_NOT_CONNECTED){ + return result; + }else{ + throw convertErrorToLlcpException(result); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getRemoteSocketRw(): ", e); + return 0; + } + } + + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/NdefMessage.aidl b/core/java/com/trustedlogic/trustednfc/android/NdefMessage.aidl new file mode 100644 index 0000000..e60f4e8 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NdefMessage.aidl @@ -0,0 +1,19 @@ +/* + * 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.trustedlogic.trustednfc.android; + +parcelable NdefMessage; diff --git a/core/java/com/trustedlogic/trustednfc/android/NdefMessage.java b/core/java/com/trustedlogic/trustednfc/android/NdefMessage.java new file mode 100644 index 0000000..f03b604 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NdefMessage.java @@ -0,0 +1,160 @@ +/* + * 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. + */ + +/** + * File : NDEFMessage.java + * Original-Author : Trusted Logic S.A. (Jeremie Corbier) + * Created : 05-10-2009 + */ + +package com.trustedlogic.trustednfc.android; + +import java.util.LinkedList; +import java.util.ListIterator; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents an NDEF message as specified by the <a + * href="http://www.nfc-forum.org/">NFC Forum</a>. + * + * @see NdefRecord + * + * @since AA01.04 + * @hide + */ +public class NdefMessage implements Parcelable { + /* Flag values */ + private static final int FLAG_MB = 0x80; + private static final int FLAG_ME = 0x40; + private static final int FLAG_CF = 0x20; + private static final int FLAG_SR = 0x10; + private static final int FLAG_IL = 0x08; + + /** + * Array of {@link NdefRecord} composing this message. + */ + private NdefRecord[] mRecords; + + /** + * Builds an NDEF message. + * + * @param data raw NDEF message data + * + * @throws NFCException + */ + public NdefMessage(byte[] data) throws NfcException { + if (parseNdefMessage(data) == -1) + throw new NfcException("Error while parsing NDEF message"); + } + + /** + * Builds an NDEF message. + * + * @param records + * an array of already created NDEF records + */ + public NdefMessage(NdefRecord[] records) { + mRecords = new NdefRecord[records.length]; + + System.arraycopy(records, 0, mRecords, 0, records.length); + } + + /** + * Returns the NDEF message as a byte array. + * + * @return the message as a byte array + */ + public byte[] toByteArray() { + if ((mRecords == null) || (mRecords.length == 0)) + return null; + + byte[] msg = {}; + + for (int i = 0; i < mRecords.length; i++) { + byte[] record = mRecords[i].toByteArray(); + byte[] tmp = new byte[msg.length + record.length]; + + /* Make sure the Message Begin flag is set only for the first record */ + if (i == 0) + record[0] |= FLAG_MB; + else + record[0] &= ~FLAG_MB; + + /* Make sure the Message End flag is set only for the last record */ + if (i == (mRecords.length - 1)) + record[0] |= FLAG_ME; + else + record[0] &= ~FLAG_ME; + + System.arraycopy(msg, 0, tmp, 0, msg.length); + System.arraycopy(record, 0, tmp, msg.length, record.length); + + msg = tmp; + } + + return msg; + } + + /** + * Returns an array of {@link NdefRecord} composing this message. + * + * @return mRecords + * + * @since AA02.01 + */ + public NdefRecord[] getRecords(){ + return mRecords; + } + + private native int parseNdefMessage(byte[] data); + + /** + * (Parcelable) Describe the parcel + * {@hide} + */ + public int describeContents() { + return 0; + } + + /** + * (Parcelable) Convert current object to a Parcel + * {@hide} + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRecords.length); + dest.writeTypedArray(mRecords, 0); + } + + /** + * Creator class, needed when implementing from Parcelable + * {@hide} + */ + public static final Parcelable.Creator<NdefMessage> CREATOR = new Parcelable.Creator<NdefMessage>() { + public NdefMessage createFromParcel(Parcel in) { + int recordsLength = in.readInt(); + NdefRecord[] records = new NdefRecord[recordsLength]; + in.readTypedArray(records, NdefRecord.CREATOR); + return new NdefMessage(records); + } + + public NdefMessage[] newArray(int size) { + return new NdefMessage[size]; + } + }; + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/NdefRecord.aidl b/core/java/com/trustedlogic/trustednfc/android/NdefRecord.aidl new file mode 100644 index 0000000..9d95174 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NdefRecord.aidl @@ -0,0 +1,19 @@ +/* + * 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.trustedlogic.trustednfc.android; + +parcelable NdefRecord; diff --git a/core/java/com/trustedlogic/trustednfc/android/NdefRecord.java b/core/java/com/trustedlogic/trustednfc/android/NdefRecord.java new file mode 100644 index 0000000..a0257fe --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NdefRecord.java @@ -0,0 +1,293 @@ +/* + * 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. + */ + +/** + * File : NdefRecord.java + * Original-Author : Trusted Logic S.A. (Jeremie Corbier) + * Created : 05-10-2009 + */ + +package com.trustedlogic.trustednfc.android; + +import android.location.Location; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An NDEF record as specified by the <a href="http://www.nfc-forum.org/">NFC + * Forum</a>. + * + * @see NdefMessage + * + * @since AA01.04 + * @hide + */ +public class NdefRecord implements Parcelable { + + /** + * Type Name Format - Empty record + */ + public static final short TNF_EMPTY = 0x0; + + /** + * Type Name Format - NFC Forum-defined type + */ + public static final short TNF_WELL_KNOWN_TYPE = 0x1; + + /** + * Type Name Format - RFC2045 MIME type + */ + public static final short TNF_MIME_MEDIA_TYPE = 0x2; + + /** + * Type Name Format - Absolute URI + */ + public static final short TNF_ABSOLUTE_URI = 0x3; + + /** + * Type Name Format - User-defined type + */ + public static final short TNF_EXTERNAL_TYPE = 0x4; + + /** + * Type Name Format - Unknown type + */ + public static final short TNF_UNKNOWN = 0x5; + + /** + * Type Name Format - Unchanged. This TNF is used for chunked records, so + * that middle records inherits from the first record's type. + */ + public static final short TNF_UNCHANGED = 0x6; + + /** + * NFC Forum-defined Type - Smart Poster + */ + public static final byte[] TYPE_SMART_POSTER = { 0x53, 0x70 }; + + /** + * NFC Forum-defined Type - Text + */ + public static final byte[] TYPE_TEXT = { 0x54 }; + + /** + * NFC Forum-defined Type - URI + */ + public static final byte[] TYPE_URI = { 0x55 }; + + /** + * NFC Forum-defined Global Type - Connection Handover Request + */ + public static final byte[] TYPE_HANDOVER_REQUEST = { 0x48, 0x72 }; + + /** + * NFC Forum-defined Global Type - Connection Handover Select + */ + public static final byte[] TYPE_HANDOVER_SELECT = { 0x48, 0x73 }; + + /** + * NFC Forum-defined Global Type - Connection Handover Carrier + */ + public static final byte[] TYPE_HANDOVER_CARRIER = { 0x48, 0x63 }; + + /** + * NFC Forum-defined Local Type - Alternative Carrier + */ + public static final byte[] TYPE_ALTERNATIVE_CARRIER = { 0x61, 0x63 }; + + /* Flag values */ + private static final int FLAG_MB = 0x80; + private static final int FLAG_ME = 0x40; + private static final int FLAG_CF = 0x20; + private static final int FLAG_SR = 0x10; + private static final int FLAG_IL = 0x08; + + /** + * Record Flags + */ + private short mFlags = 0; + + /** + * Record Type Name Format + */ + private short mTnf = 0; + + /** + * Record Type + */ + private byte[] mType = null; + + /** + * Record Identifier + */ + private byte[] mId = null; + + /** + * Record Payload + */ + private byte[] mPayload = null; + + /** + * Creates an NdefRecord given its Type Name Format, its type, its id and + * its. + * + * @param tnf + * Type Name Format + * @param type + * record type + * @param id + * record id (optional, can be null) + * @param data + * record payload + */ + public NdefRecord(short tnf, byte[] type, byte[] id, byte[] data) { + + /* generate flag */ + mFlags = FLAG_MB | FLAG_ME; + + /* Determine if it is a short record */ + if(data.length < 0xFF) + { + mFlags |= FLAG_SR; + } + + /* Determine if an id is present */ + if(id.length != 0) + { + mFlags |= FLAG_IL; + } + + mTnf = tnf; + mType = (byte[]) type.clone(); + mId = (byte[]) id.clone(); + mPayload = (byte[]) data.clone(); + } + + /** + * Appends data to the record's payload. + * + * @param data + * Data to be added to the record. + */ + public void appendPayload(byte[] data) { + byte[] newPayload = new byte[mPayload.length + data.length]; + + System.arraycopy(mPayload, 0, newPayload, 0, mPayload.length); + System.arraycopy(data, 0, newPayload, mPayload.length, data.length); + + mPayload = newPayload; + } + + /** + * Returns record as a byte array. + * + * @return record as a byte array. + */ + public byte[] toByteArray() { + return generate(mFlags, mTnf, mType, mId, mPayload); + } + + private native byte[] generate(short flags, short tnf, byte[] type, + byte[] id, byte[] data); + + /** + * (Parcelable) Describe the parcel + * {@hide} + */ + public int describeContents() { + return 0; + } + + /** + * (Parcelable) Convert current object to a Parcel + * {@hide} + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mTnf); + dest.writeInt(mType.length); + dest.writeByteArray(mType); + dest.writeInt(mId.length); + dest.writeByteArray(mId); + dest.writeInt(mPayload.length); + dest.writeByteArray(mPayload); + } + + /** + * Creator class, needed when implementing from Parcelable + * {@hide} + */ + public static final Parcelable.Creator<NdefRecord> CREATOR = new Parcelable.Creator<NdefRecord>() { + public NdefRecord createFromParcel(Parcel in) { + // TNF + short tnf = (short)in.readInt(); + // Type + int typeLength = in.readInt(); + byte[] type = new byte[typeLength]; + in.readByteArray(type); + // ID + int idLength = in.readInt(); + byte[] id = new byte[idLength]; + in.readByteArray(id); + // Payload + int payloadLength = in.readInt(); + byte[] payload = new byte[payloadLength]; + in.readByteArray(payload); + + return new NdefRecord(tnf, type, id, payload); + } + + public NdefRecord[] newArray(int size) { + return new NdefRecord[size]; + } + }; + + /** + * Returns record TNF + * + * @return mTnf + */ + public int getTnf(){ + return mTnf; + } + + /** + * Returns record TYPE + * + * @return mType + */ + public byte[] getType(){ + return mType; + } + + /** + * Returns record ID + * + * @return mId + */ + public byte[] getId(){ + return mId; + } + + /** + * Returns record Payload + * + * @return mPayload + */ + public byte[] getPayload(){ + return mPayload; + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/NdefTag.java b/core/java/com/trustedlogic/trustednfc/android/NdefTag.java new file mode 100644 index 0000000..1d99241 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NdefTag.java @@ -0,0 +1,126 @@ +/* + * 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. + */ + +/** + * File : NDEFTag.java + * Original-Author : Trusted Logic S.A. (Jeremie Corbier) + * Created : 04-12-2009 + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import android.os.RemoteException; +import android.util.Log; + +/** + * NdefTag represents tags complying with the NFC Forum's NFC Data Exchange + * Format. + * + * @since AA01.04 + * @hide + */ +public class NdefTag extends NfcTag { + + private static final String TAG = "NdefTag"; + + + public NdefTag(NfcTag tag){ + super(tag.mService,tag.mHandle); + this.isConnected = tag.isConnected; + this.isClosed = tag.isClosed; + tag.isClosed = false; + } + + /** + * Internal constructor for the NfcNdefTag class. + * + * @param service The entry point to the Nfc Service for NfcNdefTag class. + * @param handle The handle returned by the NFC service and used to identify + * the tag in subsequent calls. + * @hide + */ + NdefTag(INfcTag service, int handle) { + super(service, handle); + } + + /** + * Read NDEF data from an NDEF tag. + * + * @return the NDEF message read from the tag. + * @throws NfcException if the tag is not NDEF-formatted. + * @throws IOException if the target has been lost or the connection has + * been closed. + * @see NdefMessage + */ + public NdefMessage read() throws NfcException, IOException { + // Check state + checkState(); + + //Check if the tag is Ndef compliant + if(isNdef != true){ + isNdef = isNdef(); + if(isNdef != true) { + throw new NfcException("Tag is not NDEF compliant"); + } + } + + // Perform transceive + try { + NdefMessage msg = mService.read(mHandle); + if (msg == null) { + throw new IOException("NDEF read failed"); + } + return msg; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in read(): ", e); + return null; + } + } + + /** + * Write NDEF data to an NDEF-compliant tag. + * + * @param msg NDEF message to be written to the tag. + * @throws NfcException if the tag is not NDEF formatted. + * @throws IOException if the target has been lost or the connection has + * been closed. + * @see NdefMessage + */ + public void write(NdefMessage msg) throws NfcException, IOException { + // Check state + checkState(); + + //Check if the tag is Ndef compliant + if(isNdef != true){ + isNdef = isNdef(); + if(isNdef != true) { + throw new NfcException("Tag is not NDEF compliant"); + } + } + + // Perform transceive + try { + boolean isSuccess = mService.write(mHandle, msg); + if (!isSuccess) { + throw new IOException("NDEF write failed"); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in write(): ", e); + } + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/NfcException.java b/core/java/com/trustedlogic/trustednfc/android/NfcException.java new file mode 100644 index 0000000..2497c15 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NfcException.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +/** + * File : NFCException.java + * Original-Author : Trusted Logic S.A. (Jeremie Corbier) + * Created : 26-08-2009 + */ + +package com.trustedlogic.trustednfc.android; + +/** + * Generic exception thrown in case something unexpected happened during the + * NFCManager operations. + * + * @since AA01.04 + * @hide + */ +public class NfcException extends Exception { + /** + * Constructs a new NfcException with the current stack trace and the + * specified detail message. + * + * @param s the detail message for this exception. + */ + public NfcException(String s) { + super(s); + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/NfcManager.java b/core/java/com/trustedlogic/trustednfc/android/NfcManager.java new file mode 100644 index 0000000..98ab5bf --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NfcManager.java @@ -0,0 +1,656 @@ +/* + * 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. + */ + +/** + * File : NfcManager.java + * Original-Author : Trusted Logic S.A. (Jeremie Corbier) + * Created : 26-08-2009 + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.media.MiniThumbFile; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; + +//import android.util.Log; + +/** + * This class provides the primary API for managing all aspects of NFC. Get an + * instance of this class by calling + * Context.getSystemService(Context.NFC_SERVICE). + * @hide + */ +public final class NfcManager { + /** + * Tag Reader Discovery mode + */ + private static final int DISCOVERY_MODE_TAG_READER = 0; + + /** + * NFC-IP1 Peer-to-Peer mode Enables the manager to act as a peer in an + * NFC-IP1 communication. Implementations should not assume that the + * controller will end up behaving as an NFC-IP1 target or initiator and + * should handle both cases, depending on the type of the remote peer type. + */ + private static final int DISCOVERY_MODE_NFCIP1 = 1; + + /** + * Card Emulation mode Enables the manager to act as an NFC tag. Provided + * that a Secure Element (an UICC for instance) is connected to the NFC + * controller through its SWP interface, it can be exposed to the outside + * NFC world and be addressed by external readers the same way they would + * with a tag. + * <p> + * Which Secure Element is exposed is implementation-dependent. + * + * @since AA01.04 + */ + private static final int DISCOVERY_MODE_CARD_EMULATION = 2; + + /** + * Used as Parcelable extra field in + * {@link com.trustedlogic.trustednfc.android.NfcManager#NDEF_TAG_DISCOVERED_ACTION} + * . It contains the NDEF message read from the NDEF tag discovered. + */ + public static final String NDEF_MESSAGE_EXTRA = "com.trustedlogic.trustednfc.android.extra.NDEF_MESSAGE"; + + /** + * Broadcast Action: a NDEF tag has been discovered. + * <p> + * Always contains the extra field + * {@link com.trustedlogic.trustednfc.android.NfcManager#NDEF_MESSAGE_EXTRA}. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_NOTIFY permission. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String NDEF_TAG_DISCOVERED_ACTION = "com.trustedlogic.trustednfc.android.action.NDEF_TAG_DISCOVERED"; + + /** + * Used as byte array extra field in + * {@link com.trustedlogic.trustednfc.android.NfcManager#TRANSACTION_DETECTED_ACTION} + * . It contains the AID of the applet concerned by the transaction. + */ + public static final String AID_EXTRA = "com.trustedlogic.trustednfc.android.extra.AID"; + + /** + * Broadcast Action: a transaction with a secure element has been detected. + * <p> + * Always contains the extra field + * {@link com.trustedlogic.trustednfc.android.NfcManager#AID_EXTRA} + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_NOTIFY permission + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String TRANSACTION_DETECTED_ACTION = "com.trustedlogic.trustednfc.android.action.TRANSACTION_DETECTED"; + + /** + * LLCP link status: The LLCP link is activated. + * + * @since AA02.01 + */ + public static final int LLCP_LINK_STATE_ACTIVATED = 0; + + /** + * LLCP link status: The LLCP link is deactivated. + * + * @since AA02.01 + */ + public static final int LLCP_LINK_STATE_DEACTIVATED = 1; + + /** + * Used as int extra field in + * {@link com.trustedlogic.trustednfc.android.NfcManager#LLCP_LINK_STATE_CHANGED_ACTION} + * . It contains the new state of the LLCP link. + */ + public static final String LLCP_LINK_STATE_CHANGED_EXTRA = "com.trustedlogic.trustednfc.android.extra.LLCP_LINK_STATE"; + + /** + * Broadcast Action: the LLCP link state changed. + * <p> + * Always contains the extra field + * {@link com.trustedlogic.trustednfc.android.NfcManager#LLCP_LINK_STATE_CHANGED_EXTRA}. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_LLCP permission. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String LLCP_LINK_STATE_CHANGED_ACTION = "com.trustedlogic.trustednfc.android.action.LLCP_LINK_STATE_CHANGED"; + + private static final String TAG = "NfcManager"; + + private Handler mHandler; + + private INfcManager mService; + + private INfcTag mNfcTagService; + + private IP2pTarget mP2pTargetService; + + private IP2pInitiator mP2pInitiatorService; + + private ILlcpSocket mLlcpSocketService; + + private ILlcpConnectionlessSocket mLlcpConnectionlessSocketService; + + private ILlcpServiceSocket mLlcpServiceSocketService; + + static NfcException convertErrorToNfcException(int errorCode) { + return convertErrorToNfcException(errorCode, null); + } + + static NfcException convertErrorToNfcException(int errorCode, String message) { + if (message == null) { + message = ""; + } else { + message = " (" + message + ")"; + } + + switch (errorCode) { + case ErrorCodes.ERROR_BUSY: + return new NfcException("Another operation is already pending" + message); + case ErrorCodes.ERROR_CANCELLED: + return new NfcException("Operation cancelled" + message); + case ErrorCodes.ERROR_TIMEOUT: + return new NfcException("Operation timed out" + message); + case ErrorCodes.ERROR_SOCKET_CREATION: + return new NfcException("Error during the creation of an Llcp socket:" + message); + case ErrorCodes.ERROR_SAP_USED: + return new NfcException("Error SAP already used:" + message); + case ErrorCodes.ERROR_SERVICE_NAME_USED: + return new NfcException("Error Service Name already used:" + message); + case ErrorCodes.ERROR_SOCKET_OPTIONS: + return new NfcException("Error Socket options:" + message); + case ErrorCodes.ERROR_INVALID_PARAM: + return new NfcException("Error Set Properties: invalid param" + message); + case ErrorCodes.ERROR_NFC_ON: + return new NfcException("Error Set Properties : NFC is ON" + message); + case ErrorCodes.ERROR_NOT_INITIALIZED: + return new NfcException("NFC is not running " + message); + case ErrorCodes.ERROR_SE_ALREADY_SELECTED: + return new NfcException("Secure Element already connected" + message); + case ErrorCodes.ERROR_NO_SE_CONNECTED: + return new NfcException("No Secure Element connected" + message); + case ErrorCodes.ERROR_SE_CONNECTED: + return new NfcException("A secure Element is already connected" + message); + default: + return new NfcException("Unkown error code " + errorCode + message); + } + } + + /** + * @hide + */ + public NfcManager(INfcManager service, Handler handler) { + mService = service; + mHandler = handler; + try { + mNfcTagService = mService.getNfcTagInterface(); + mP2pInitiatorService = mService.getP2pInitiatorInterface(); + mP2pTargetService = mService.getP2pTargetInterface(); + mLlcpServiceSocketService = mService.getLlcpServiceInterface(); + mLlcpConnectionlessSocketService = mService.getLlcpConnectionlessInterface(); + mLlcpSocketService = mService.getLlcpInterface(); + } catch (RemoteException e) { + mLlcpSocketService = null; + mNfcTagService = null; + mP2pInitiatorService = null; + mP2pTargetService = null; + mLlcpConnectionlessSocketService = null; + mLlcpServiceSocketService = null; + } + } + + /** + * Return the status of the NFC feature + * + * @return mIsNfcEnabled + * @since AA02.01 + */ + public boolean isEnabled() { + try { + return mService.isEnabled(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in isEnabled(): ", e); + return false; + } + } + + /** + * Enable the NFC Feature + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_ADMIN permission + * + * @throws NfcException if the enable failed + * @since AA02.01 + */ + public void enable() throws NfcException { + try { + boolean isSuccess = mService.enable(); + if (isSuccess == false) { + throw new NfcException("NFC Service failed to enable"); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in enable(): ", e); + } + } + + /** + * Disable the NFC feature + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_ADMIN permission + * + * @throws NfcException if the disable failed + * @since AA02.01 + */ + public void disable() throws NfcException { + try { + boolean isSuccess = mService.disable(); + if (isSuccess == false) { + throw new NfcException("NFC Service failed to disable"); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in disable(): ", e); + } + } + + /** + * Get the list of the identifiers of the Secure Elements detected + * by the NFC controller. + * + * @return list a list of Secure Element identifiers. + * @see #getSelectedSecureElement + * @see #selectSecureElement(int) + * @see #deselectSecureElement + * @since AA02.01 + */ + public int[] getSecureElementList() { + try { + return mService.getSecureElementList(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getSecureElementList(): ", e); + return null; + } + } + + /** + * Get the identifier of the currently selected secure element. + * + * @return id identifier of the currently selected Secure Element. 0 if none. + * @see #getSecureElementList + * @see #selectSecureElement(int) + * @see #deselectSecureElement + * @since AA02.01 + */ + public int getSelectedSecureElement() { + try { + return mService.getSelectedSecureElement(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getSelectedSecureElement(): ", e); + return -1; + } + } + + /** + * Select a specific Secure Element by its identifier. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_ADMIN permission + * + * @throws NfcException if a or this secure element is already selected + * @see #getSecureElementList + * @see #getSelectedSecureElement + * @see #deselectSecureElement + * @since AA02.01 + */ + public void selectSecureElement(int seId) throws NfcException { + try { + int status = mService.selectSecureElement(seId); + if(status != ErrorCodes.SUCCESS){ + throw convertErrorToNfcException(status); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in selectSecureElement(): ", e); + } + } + + /** + * Deselect the currently selected Secure Element + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_ADMIN permission + * + * @throws NfcException if no secure Element is selected + * @see #getSecureElementList + * @see #getSelectedSecureElement + * @see #selectSecureElement(int) + * @since AA02.01 + */ + public void deselectSecureElement() throws NfcException { + try { + int status = mService.deselectSecureElement(); + if(status != ErrorCodes.SUCCESS){ + throw convertErrorToNfcException(status); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in deselectSecureElement(): ", e); + } + } + + /** + * Open a connection with a remote NFC peer + * + * This method does not return while no remote NFC peer enters the field. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_RAW permission + * + * @return P2pDevice object to be used to communicate with the detected + * peer. + * @throws IOException if the target has been lost or the connection has + * been closed. + * @throws NfcException if an open is already started + * @see P2pDevice + * @see #getOpenTimeout + * @see #setOpenTimeout(int) + * @see #cancel + * @since AA02.01 + */ + public P2pDevice openP2pConnection() throws IOException, NfcException { + try { + int handle = mService.openP2pConnection(); + // Handle potential errors + if (ErrorCodes.isError(handle)) { + if (handle == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToNfcException(handle); + } + } + // Build the public NfcTag object, depending on its type + if (mP2pTargetService.getMode(handle) == P2pDevice.MODE_P2P_TARGET) { + return new P2pTarget(mP2pTargetService, handle); + } else { + return new P2pInitiator(mP2pInitiatorService, handle); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openTagConnection(): ", e); + return null; + } + } + + /** + * Open a connection with a tag + * + * This method does not return while no tag enters the field. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_RAW permission + * + * @return tag object to be use to communicate with the detected NfcTag. + * @throws IOException if the target has been lost or the connection has + * been closed. + * @throws NfcException if an open is already started + * @see NfcTag + * @see #getOpenTimeout + * @see #setOpenTimeout(int) + * @see #cancel + * @since AA02.01 + */ + public NfcTag openTagConnection() throws IOException, NfcException { + try { + int handle = mService.openTagConnection(); + // Handle potential errors + if (ErrorCodes.isError(handle)) { + if (handle == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToNfcException(handle); + } + } + // Build the public NfcTag object + return new NfcTag(mNfcTagService, handle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openTagConnection(): ", e); + return null; + } + } + + /** + * Set the timeout for open requests + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_RAW permission + * + * @param timeout value of the timeout for open request + * @see #openP2pConnection + * @see #openTagConnection + * @see #getOpenTimeout + * @since AA02.01 + */ + public void setOpenTimeout(int timeout) { + try { + mService.setOpenTimeout(timeout); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in setOpenTimeout(): ", e); + } + } + + /** + * Get the timeout value of open requests + * + * @return mTimeout + * @see #setOpenTimeout(int) + * @since AA02.01 + */ + public int getOpenTimeout() { + try { + return mService.getOpenTimeout(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getOpenTimeout(): ", e); + return 0; + } + } + + /** + * Cancel an openTagConnection or an openP2pConnection started + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_RAW permission + * + * @see #openP2pConnection + * @see #openTagConnection + * @since AA02.01 + */ + public void cancel() { + try { + mService.cancel(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in cancel(): ", e); + } + } + + /** + * Creates a connectionless socket for a LLCP link and set its Service + * Access Point number (SAP) + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_LLCP permission + * + * @param sap Service Access Point number related to the created + * Connectionless socket. + * @return LlcpConnectionlessSocket object to be used in a LLCP + * Connectionless communication. + * @throws IOException if the socket creation failed + * @throws NfcException if socket ressources are insufficicent + * @see LlcpConnectionlessSocket + * @since AA02.01 + */ + public LlcpConnectionlessSocket createLlcpConnectionlessSocket(int sap) throws IOException, + NfcException { + + try { + int handle = mService.createLlcpConnectionlessSocket(sap); + // Handle potential errors + if (ErrorCodes.isError(handle)) { + if (handle == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToNfcException(handle); + } + } + + // Build the public LlcpConnectionLess object + return new LlcpConnectionlessSocket(mLlcpConnectionlessSocketService, handle); + + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in createLlcpConnectionlessSocket(): ", e); + return null; + } + } + + /** + * Creates a LlcpServiceSocket for a LLCP link, set its Service Access Point + * number (SAP). + * <p> + * During a LLCP communication, the LlcpServiceSocket will create LlcpSocket + * to communicate with incoming LLCP clients. For that, a server socket need + * to have some informations as a working buffer length in order to handle + * incoming data and some options to define the LLCP communication. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_LLCP permission + * + * @param sap + * @param sn Service Name of the LlcpServiceSocket + * @param miu Maximum Information Unit (MIU) for a LlcpSocket created by the + * LlcpServiceSocket + * @param rw Receive Window (RW) for a LlcpSocket created by the + * LlcpServiceSocket + * @param linearBufferLength size of the memory space needed to handle + * incoming data for every LlcpSocket created. + * @return LlcpServiceSocket object to be used as a LLCP Service in a + * connection oriented communication. + * @throws IOException if the socket creation failed + * @throws NfcException if socket ressources are insufficicent + * @see LlcpServiceSocket + * @since AA02.01 + */ + public LlcpServiceSocket createLlcpServiceSocket(int sap, String sn, int miu, int rw, + int linearBufferLength) throws IOException, NfcException { + try { + int handle = mService.createLlcpServiceSocket(sap, sn, miu, rw, linearBufferLength); + // Handle potential errors + if (ErrorCodes.isError(handle)) { + if (handle == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToNfcException(handle); + } + } + + // Build the public LlcpServiceSocket object + return new LlcpServiceSocket(mLlcpServiceSocketService, mLlcpSocketService, handle); + + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in createLlcpServiceSocket(): ", e); + return null; + } + } + + /** + * Creates a LlcpSocket for a LLCP link with a specific Service Access Point + * number (SAP) + * <p> + * A LlcpSocket need to have a linear buffer in order to handle incoming + * data. This linear buffer will be used to store incoming data as a stream. + * Data will be readable later. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_LLCP permission + * + * @param sap Service Access Point number for the created socket + * @param miu Maximum Information Unit (MIU) of the communication socket + * @param rw Receive Window (RW) of the communication socket + * @param linearBufferLength size of the memory space needed to handle + * incoming data with this socket + * @throws IOException if the socket creation failed + * @throws NfcException if socket ressources are insufficicent + * @see LlcpSocket + * @since AA02.01 + */ + public LlcpSocket createLlcpSocket(int sap, int miu, int rw, int linearBufferLength) + throws IOException, NfcException { + try { + int handle = mService.createLlcpSocket(sap, miu, rw, linearBufferLength); + // Handle potential errors + if (ErrorCodes.isError(handle)) { + if (handle == ErrorCodes.ERROR_IO) { + throw new IOException(); + } else { + throw convertErrorToNfcException(handle); + } + } + // Build the public LlcpSocket object + return new LlcpSocket(mLlcpSocketService, handle); + + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in createLlcpSocket(): ", e); + return null; + } + } + + /** + * Set different parameters like the NCIP General bytes, the LLCP link + * parameters and all tag discovery parameters. + * <p class="note"> + * <strong>Note:</strong> Requires the NFC_ADMIN permission + * + * @param param parameter to be updated with a new value + * @param value new value of the parameter + * @throws NfcException if incorrect parameters of NFC is ON + * @since AA02.01 + */ + public void setProperties(String param, String value) throws NfcException { + try { + int result = mService.setProperties(param, value); + // Handle potential errors + if (ErrorCodes.isError(result)) { + throw convertErrorToNfcException(result); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in setProperties(): ", e); + } + } + + /** + * Get the value of different parameters like the NCFIP General bytes, the + * LLCP link parameters and all tag discovery parameters. + * + * @param param parameter to be updated + * @return String value of the requested parameter + * @throws RemoteException + * @since AA02.01 + */ + public String getProperties(String param) { + String value; + try { + value = mService.getProperties(param); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getProperties(): ", e); + return null; + } + return value; + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/NfcTag.java b/core/java/com/trustedlogic/trustednfc/android/NfcTag.java new file mode 100644 index 0000000..798c7e4 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/NfcTag.java @@ -0,0 +1,250 @@ +/* + * 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. + */ + +/** + * File : NFCTag.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + * Created : 26-02-2010 + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import android.os.RemoteException; +import android.util.Log; + +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +/** + * This class represents tags with no known formatting. One can use the method + * {@link #isNdef()} to determine if the tag can store NDEF-formatted messages. + * <p> + * + * <pre class="prettyprint"> + * if (tag.isNdef()) { + * NdefTag ndefTag = (NdefTag) tag; + * NdefMessage msg = ndefTag.read(); + * } + * </pre> + * + * @since AA01.04 + * @see NdefMessage + * @hide + */ +public class NfcTag { + + private static final String TAG = "NfcTag"; + + /** + * The handle returned by the NFC service and used to identify the tag in + * every call of this class. + * + * @hide + */ + protected int mHandle; + + /** + * The entry point for tag operations. + * + * @hide + */ + protected INfcTag mService; + + /** + * Flag set when the object is closed and thus not usable any more. + * + * @hide + */ + protected boolean isClosed = false; + + /** + * Flag set when the tag is connected. + * + * @hide + */ + protected boolean isConnected = false; + + /** + * Flag set when a check NDEF is performed. + * + * @hide + */ + protected boolean isNdef = false; + + /** + * Check if tag is still opened. + * + * @return data sent by the P2pInitiator. + * @throws NfcException if accessing a closed target. + * + * @hide + */ + public void checkState() throws NfcException { + if (isClosed) { + throw new NfcException("Tag has been closed."); + } + if (!isConnected) { + throw new NfcException("Tag is not connected."); + } + } + + /** + * Internal constructor for the NfcTag class. + * + * @param service The entry point to the Nfc Service for NfcTag class. + * @param handle The handle returned by the NFC service and used to identify + * the tag in subsequent calls. + * @hide + */ + NfcTag(INfcTag service, int handle) { + this.mService = service; + this.mHandle = handle; + } + + /** + * Connects to the tag. This shall be called prior to any other operation on + * the tag. + * + * @throws IOException if the tag has been lost or the connection has been + * closed. + * @throws nfcException if the tag is already in connected state. + */ + public void connect() throws NfcException, IOException { + // Check state + if (isClosed) { + throw new NfcException("Tag has been closed."); + } + if (isConnected) { + throw new NfcException("Already connected"); + } + + // Perform connect + try { + int result = mService.connect(mHandle); + if (ErrorCodes.isError(result)) { + if (result == ErrorCodes.ERROR_IO) { + throw new IOException("Failed to connect"); + } + else { + throw NfcManager.convertErrorToNfcException(result); + } + } + isConnected = true; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in connect(): ", e); + } + } + + /** + * Disconnects from the tag. This must be called so that other targets can + * be discovered. It restarts the NFC discovery loop. + * + * @throws NfcException if the tag is already in disconnected state or not connected + */ + public void close() throws NfcException { + // Check state + checkState(); + + try { + mService.close(mHandle); + isClosed = true; + isConnected = false; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in close(): ", e); + } + } + + /** + * Exchanges raw data with the tag, whatever the tag type. + * + * To exchange APDUs with a ISO14443-4-compliant tag, the data parameter + * must be filled with the C-APDU (CLA, INS, P1, P2 [, ...]). The returned + * data consists of the R-APDU ([...,] SW1, SW2). + * + * @param data data to be sent to the tag + * @return data sent in response by the tag + * @throws IOException if the tag has been lost or the connection has been + * closed. + * @throws NfcException in case of failure within the stack + */ + public byte[] transceive(byte[] data) throws IOException, NfcException { + // Check state + checkState(); + + // Perform transceive + try { + byte[] response = mService.transceive(mHandle, data); + if (response == null) { + throw new IOException("Transceive failed"); + } + return response; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in transceive(): ", e); + return null; + } + } + + /** + * Checks whether tag is NDEF-compliant or not. + * + * @return true if the tag is NDEF-compliant, false otherwise + * @throws NfcException in case an error occurred when trying to determine + * whether the tag is NDEF-compliant + */ + public boolean isNdef() throws NfcException { + // Check state + checkState(); + + // Perform Check Ndef + try { + isNdef = mService.isNdef(mHandle); + return isNdef; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in isNdef(): ", e); + return false; + } + } + + /** + * Returns target type. constants. + * + * @return tag type. + */ + public String getType() { + try { + return mService.getType(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getType(): ", e); + return null; + } + } + + /** + * Returns target UID. + * + * @return tag UID. + */ + public byte[] getUid() { + try { + return mService.getUid(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getType(): ", e); + return null; + } + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/P2pDevice.java b/core/java/com/trustedlogic/trustednfc/android/P2pDevice.java new file mode 100644 index 0000000..65800f2 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/P2pDevice.java @@ -0,0 +1,89 @@ +/* + * 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. + */ + +/** + * File : P2PDevice.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + * Created : 26-02-2010 + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +/** + * P2pDevice is the abstract base class for all supported P2P targets the + * NfcManager can handle. + * @hide + */ +public abstract class P2pDevice { + + /** + * Peer-to-Peer Target. + */ + public static final short MODE_P2P_TARGET = 0x00; + + /** + * Peer-to-Peer Initiator. + */ + public static final short MODE_P2P_INITIATOR = 0x01; + + /** + * Invalid target type. + */ + public static final short MODE_INVALID = 0xff; + + /** + * Target handle, used by native calls. + * @hide + */ + protected int mHandle; + + /** + * Flag set when the object is closed and thus not usable any more. + * @hide + */ + protected boolean isClosed = false; + + /** + * Prevent default constructor to be public. + * @hide + */ + protected P2pDevice() { + } + + /** + * Returns the remote NFC-IP1 General Bytes. + * + * @return remote general bytes + * @throws IOException + */ + public byte[] getGeneralBytes() throws IOException { + // Should not be called directly (use subclasses overridden method instead) + return null; + } + + /** + * Returns target type. The value returned can be one of the TYPE_* + * constants. + * + * @return target type. + */ + public int getMode() { + // Should not be called directly (use subclasses overridden method instead) + return MODE_INVALID; + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/P2pInitiator.java b/core/java/com/trustedlogic/trustednfc/android/P2pInitiator.java new file mode 100644 index 0000000..0f28ae0 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/P2pInitiator.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +/** + * File : P2PInitiator.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +import android.os.RemoteException; +import android.util.Log; + +/** + * P2pInitiator represents the initiator in an NFC-IP1 peer-to-peer + * communication. + * + * @see P2pTarget + * @since AA02.01 + * @hide + */ +public class P2pInitiator extends P2pDevice { + + private static final String TAG = "P2pInitiator"; + + /** + * The entry point for P2P tag operations. + * @hide + */ + private IP2pInitiator mService; + + /** + * Internal constructor for the P2pInitiator class. + * + * @param handle The handle returned by the NFC service and used to identify + * the tag in subsequent calls. + * + * @hide + */ + P2pInitiator(IP2pInitiator service, int handle) { + this.mService = service; + this.mHandle = handle; + } + + /** + * Receives data from a P2pInitiator. + * + * @return data sent by the P2pInitiator. + * @throws IOException if the target has been lost or if the connection has + * been closed. + */ + public byte[] receive() throws IOException { + try { + byte[] result = mService.receive(mHandle); + if (result == null) { + throw new IOException("Tag has been lost"); + } + return result; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in receive(): ", e); + return null; + } + } + + /** + * Sends data to a P2pInitiator. + * + * @param data data to be sent to the P2pInitiator. + * @throws IOException if the target has been lost or if the connection has + * been closed. + */ + public void send(byte[] data) throws IOException { + try { + boolean isSuccess = mService.send(mHandle, data); + if (!isSuccess) { + throw new IOException("Tag has been lost"); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in send(): ", e); + } + } + + @Override + public byte[] getGeneralBytes() { + try { + return mService.getGeneralBytes(mHandle); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getGeneralBytes(): ", e); + return null; + } + } + + @Override + public int getMode() { + return P2pDevice.MODE_P2P_INITIATOR; + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/P2pTarget.java b/core/java/com/trustedlogic/trustednfc/android/P2pTarget.java new file mode 100644 index 0000000..b5e00db --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/P2pTarget.java @@ -0,0 +1,183 @@ +/* + * 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. + */ + +/** + * File : P2PTarget.java + * Original-Author : Trusted Logic S.A. (Daniel Tomas) + */ + +package com.trustedlogic.trustednfc.android; + +import java.io.IOException; + +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +import android.os.RemoteException; +import android.util.Log; + +/** + * P2pTarget represents the target in an NFC-IP1 peer-to-peer communication. + * + * @see P2pInitiator + * @since AA02.01 + * @hide + */ +public class P2pTarget extends P2pDevice { + + private static final String TAG = "P2pTarget"; + + /** + * The entry point for P2P tag operations. + * @hide + */ + private IP2pTarget mService; + + /** + * Flag set when the object is closed and thus not usable any more. + * @hide + */ + private boolean isClosed = false; + + /** + * Flag set when the tag is connected. + * @hide + */ + private boolean isConnected = false; + + /** + * Check if tag is still opened. + * + * @return data sent by the P2pInitiator. + * @throws NfcException if accessing a closed target. + * + * @hide + */ + public void checkState() throws NfcException { + if(isClosed) { + throw new NfcException("Tag has been closed."); + } + } + + /** + * Internal constructor for the P2pTarget class. + * + * @param handle The handle returned by the NFC service and used to identify + * the tag in subsequent calls. + * + * @hide + */ + P2pTarget(IP2pTarget service, int handle) { + this.mService = service; + this.mHandle = handle; + } + + /** + * Connects to the P2pTarget. This shall be called prior to any other + * operation on the P2pTarget. + * + * @throws NfcException + */ + public void connect() throws NfcException { + // Check state + checkState(); + if (isConnected) { + throw new NfcException("Already connected"); + } + + // Perform connect + try { + int result = mService.connect(mHandle); + if (ErrorCodes.isError(result)) { + if (result == ErrorCodes.ERROR_IO) { + throw new NfcException("Failed to connect"); + } + else { + throw NfcManager.convertErrorToNfcException(result); + } + } + isConnected = true; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in connect(): ", e); + } + } + + /** + * Disconnects from the P2p Target. This must be called so that other + * targets can be discovered. It restarts the NFC discovery loop. + * + * @throws NFCException + */ + public void disconnect() throws NfcException { + checkState(); + try { + mService.disconnect(mHandle); + isConnected = true; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in disconnect(): ", e); + } + } + + /** + * Exchanges raw data with the P2pTarget. + * + * @param data data to be sent to the P2pTarget + * @return data sent in response by the P2pTarget + * @throws IOException if the target has been lost or the connection has + * been closed. + * @throws NfcException in case of failure within the stack + */ + public byte[] transceive(byte[] data) throws IOException, NfcException { + // Check state + checkState(); + + // Perform transceive + try { + byte[] response = mService.transceive(mHandle, data); + if (response == null) { + throw new IOException("Transceive failed"); + } + return response; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in transceive(): ", e); + return null; + } + } + + /** + * Get the General bytes of the connected P2P Target + * + * @return general bytes of the connected P2P Target + * @throws IOException if the target in not in connected state + */ + public byte[] getGeneralBytes() throws IOException { + try { + if(isConnected){ + return mService.getGeneralBytes(mHandle); + }else{ + throw new IOException("Target not in connected state"); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getGeneralBytes(): ", e); + return null; + } + } + + @Override + public int getMode() { + return P2pDevice.MODE_P2P_TARGET; + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/ErrorCodes.java b/core/java/com/trustedlogic/trustednfc/android/internal/ErrorCodes.java new file mode 100644 index 0000000..ca3b7e0 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/ErrorCodes.java @@ -0,0 +1,91 @@ +/* + * 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. + */ + +/** + * File : ErrorCodes.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 26-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +/** + * This class defines all the error codes that can be returned by the service + * and producing an exception on the application level. These are needed since + * binders does not support exceptions. + * + * @hide + */ +public class ErrorCodes { + + public static boolean isError(int code) { + if (code < 0) { + return true; + } else { + return false; + } + } + + public static final int SUCCESS = 0; + + public static final int ERROR_IO = -1; + + public static final int ERROR_CANCELLED = -2; + + public static final int ERROR_TIMEOUT = -3; + + public static final int ERROR_BUSY = -4; + + public static final int ERROR_CONNECT = -5; + + public static final int ERROR_DISCONNECT = -5; + + public static final int ERROR_READ = -6; + + public static final int ERROR_WRITE = -7; + + public static final int ERROR_INVALID_PARAM = -8; + + public static final int ERROR_INSUFFICIENT_RESOURCES = -9; + + public static final int ERROR_SOCKET_CREATION = -10; + + public static final int ERROR_SOCKET_NOT_CONNECTED = -11; + + public static final int ERROR_BUFFER_TO_SMALL = -12; + + public static final int ERROR_SAP_USED = -13; + + public static final int ERROR_SERVICE_NAME_USED = -14; + + public static final int ERROR_SOCKET_OPTIONS = -15; + + public static final int ERROR_NFC_ON = -16; + + public static final int ERROR_NOT_INITIALIZED = -17; + + public static final int ERROR_SE_ALREADY_SELECTED = -18; + + public static final int ERROR_SE_CONNECTED = -19; + + public static final int ERROR_NO_SE_CONNECTED = -20; + + + + + + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpConnectionlessSocket.java b/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpConnectionlessSocket.java new file mode 100644 index 0000000..ccfbeb4 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpConnectionlessSocket.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +/** + * File : NativeLlcpConnectionLessSocket.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +import com.trustedlogic.trustednfc.android.LlcpPacket; + +/** + * LlcpConnectionlessSocket represents a LLCP Connectionless object to be used + * in a connectionless communication + * + * @since AA02.01 + * {@hide} + */ + +public class NativeLlcpConnectionlessSocket { + + private int mHandle; + + private int mSap; + + private int mLinkMiu; + + public NativeLlcpConnectionlessSocket(){; + } + + public NativeLlcpConnectionlessSocket(int sap){ + mSap = sap; + } + + public native boolean doSendTo(int sap, byte[] data); + + public native LlcpPacket doReceiveFrom(int linkMiu); + + public native boolean doClose(); + + public int getLinkMiu(){ + return mLinkMiu; + } + + public int getSap(){ + return mSap; + } + + public int getHandle(){ + return mHandle; + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpServiceSocket.java b/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpServiceSocket.java new file mode 100644 index 0000000..a01f135 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpServiceSocket.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +/** + * File : NativeLlcpServerSocket.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +/** + * LlcpServiceSocket represents a LLCP Service to be used in a + * Connection-oriented communication + * {@hide} + */ + +public class NativeLlcpServiceSocket { + + private int mHandle; + + private int mLocalMiu; + + private int mLocalRw; + + private int mLocalLinearBufferLength; + + private int mSap; + + private int mTimeout; + + private String mServiceName; + + public NativeLlcpServiceSocket(){ + + } + + public NativeLlcpServiceSocket(String serviceName){ + mServiceName = serviceName; + } + + public native NativeLlcpSocket doAccept(int timeout, int miu, int rw, int linearBufferLength); + + public native boolean doClose(); + + public int getHandle(){ + return mHandle; + } + + public void setAcceptTimeout(int timeout){ + mTimeout = timeout; + } + + public int getAcceptTimeout(){ + return mTimeout; + } + + public int getRw(){ + return mLocalRw; + } + + public int getMiu(){ + return mLocalMiu; + } + + public int getLinearBufferLength(){ + return mLocalLinearBufferLength; + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpSocket.java b/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpSocket.java new file mode 100644 index 0000000..077c5e0 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/NativeLlcpSocket.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. + */ + +/** + * File : NativeLlcpClientSocket.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +/** + * LlcpClientSocket represents a LLCP Connection-Oriented client to be used in a + * connection-oriented communication + * {@hide} + */ + +public class NativeLlcpSocket { + + private int mHandle; + + private int mSap; + + private int mLocalMiu; + + private int mLocalRw; + + private int mTimeout; + + public NativeLlcpSocket(){ + + } + + public NativeLlcpSocket(int sap, int miu, int rw){ + mSap = sap; + mLocalMiu = miu; + mLocalRw = rw; + } + + public native boolean doConnect(int nSap, int timeout); + + public native boolean doConnectBy(String sn, int timeout); + + public native boolean doClose(); + + public native boolean doSend(byte[] data); + + public native int doReceive(byte[] recvBuff); + + public native int doGetRemoteSocketMiu(); + + public native int doGetRemoteSocketRw(); + + + + public void setConnectTimeout(int timeout){ + mTimeout = timeout; + } + + public int getConnectTimeout(){ + return mTimeout; + } + + public int getSap(){ + return mSap; + } + + public int getMiu(){ + return mLocalMiu; + } + + public int getRw(){ + return mLocalRw; + } + + public int getHandle(){ + return mHandle; + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/NativeNdefTag.java b/core/java/com/trustedlogic/trustednfc/android/internal/NativeNdefTag.java new file mode 100644 index 0000000..d1e64a6 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/NativeNdefTag.java @@ -0,0 +1,36 @@ +/* + * 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. + */ + +/** + * File : NativeNdefTag.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +/** + * Native interface to the NDEF tag functions + * + * {@hide} + */ +public class NativeNdefTag { + private int mHandle; + + public native byte[] doRead(); + + public native boolean doWrite(byte[] buf); +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/NativeNfcManager.java b/core/java/com/trustedlogic/trustednfc/android/internal/NativeNfcManager.java new file mode 100644 index 0000000..2f5a0f0 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/NativeNfcManager.java @@ -0,0 +1,325 @@ +/* + * 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. + */ + +/** + * File : NativeNfcManager.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import com.trustedlogic.trustednfc.android.NfcManager; +import com.trustedlogic.trustednfc.android.NdefMessage; +import com.trustedlogic.trustednfc.android.NfcTag; + +/** + * Native interface to the NFC Manager functions {@hide} + */ +public class NativeNfcManager { + + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String INTERNAL_LLCP_LINK_STATE_CHANGED_EXTRA = "com.trustedlogic.trustednfc.android.extra.INTERNAL_LLCP_LINK_STATE"; + + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String INTERNAL_LLCP_LINK_STATE_CHANGED_ACTION = "com.trustedlogic.trustednfc.android.action.INTERNAL_LLCP_LINK_STATE_CHANGED"; + + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String INTERNAL_TARGET_DESELECTED_ACTION = "com.trustedlogic.trustednfc.android.action.INTERNAL_TARGET_DESELECTED"; + + /* Native structure */ + private int mNative; + + private Context mContext; + + private Handler mNfcHandler; + + private static final String TAG = "NativeNfcManager"; + + private static final int MSG_NDEF_TAG = 0; + + private static final int MSG_CARD_EMULATION = 1; + + private static final int MSG_LLCP_LINK_ACTIVATION = 2; + + private static final int MSG_LLCP_LINK_DEACTIVATED = 3; + + private static final int MSG_TARGET_DESELECTED = 4; + + public NativeNfcManager(Context context) { + mNfcHandler = new NfcHandler(); + mContext = context; + } + + /** + * Initializes Native structure + */ + public native boolean initializeNativeStructure(); + + /** + * Initializes NFC stack. + */ + public native boolean initialize(); + + /** + * Deinitializes NFC stack. + */ + public native boolean deinitialize(); + + /** + * Enable discory for the NdefMessage and Transaction notification + */ + public native void enableDiscovery(int mode); + + /** + * Disables an NFCManager mode of operation. Allows to disable tag reader, + * peer to peer initiator or target modes. + * + * @param mode discovery mode to enable. Must be one of the provided + * NFCManager.DISCOVERY_MODE_* constants. + */ + public native void disableDiscoveryMode(int mode); + + public native int[] doGetSecureElementList(); + + public native void doSelectSecureElement(int seID); + + public native void doDeselectSecureElement(int seID); + + public native NativeP2pDevice doOpenP2pConnection(int timeout); + + public native NativeNfcTag doOpenTagConnection(int timeout); + + public native int doGetLastError(); + + public native void doSetProperties(int param, int value); + + public native void doCancel(); + + public native NativeLlcpConnectionlessSocket doCreateLlcpConnectionlessSocket(int nSap); + + public native NativeLlcpServiceSocket doCreateLlcpServiceSocket(int nSap, String sn, int miu, + int rw, int linearBufferLength); + + public native NativeLlcpSocket doCreateLlcpSocket(int sap, int miu, int rw, + int linearBufferLength); + + public native boolean doCheckLlcp(); + + public native boolean doActivateLlcp(); + + private class NfcHandler extends Handler { + @Override + public void handleMessage(Message msg) { + + try { + switch (msg.what) { + case MSG_NDEF_TAG: + Log.d(TAG, "Checking for NDEF tag message"); + NativeNfcTag tag = (NativeNfcTag) msg.obj; + if (tag.doConnect()) { + if (tag.checkNDEF()) { + byte[] buff = tag.doRead(); + if (buff != null) { + NdefMessage msgNdef = new NdefMessage(buff); + if (msgNdef != null) { + /* Send broadcast ordered */ + Intent NdefMessageIntent = new Intent(); + NdefMessageIntent + .setAction(NfcManager.NDEF_TAG_DISCOVERED_ACTION); + NdefMessageIntent.putExtra(NfcManager.NDEF_MESSAGE_EXTRA, + msgNdef); + Log.d(TAG, "NDEF message found, broadcasting to applications"); + mContext.sendOrderedBroadcast(NdefMessageIntent, + android.Manifest.permission.NFC_NOTIFY); + /* Disconnect tag */ + tag.doAsyncDisconnect(); + } + } else { + Log.w(TAG, "Unable to read NDEF message (tag empty or not well formated)"); + /* Disconnect tag */ + tag.doAsyncDisconnect(); + } + } else { + Log.d(TAG, "Tag is *not* NDEF compliant"); + /* Disconnect tag */ + tag.doAsyncDisconnect(); + } + } else { + /* Disconnect tag */ + tag.doAsyncDisconnect(); + } + break; + case MSG_CARD_EMULATION: + Log.d(TAG, "Card Emulation message"); + byte[] aid = (byte[]) msg.obj; + /* Send broadcast ordered */ + Intent TransactionIntent = new Intent(); + TransactionIntent.setAction(NfcManager.TRANSACTION_DETECTED_ACTION); + TransactionIntent.putExtra(NfcManager.AID_EXTRA, aid); + Log.d(TAG, "Broadcasting Card Emulation event"); + mContext.sendOrderedBroadcast(TransactionIntent, + android.Manifest.permission.NFC_NOTIFY); + break; + + case MSG_LLCP_LINK_ACTIVATION: + NativeP2pDevice device = (NativeP2pDevice) msg.obj; + + Log.d(TAG, "LLCP Activation message"); + + if (device.getMode() == NativeP2pDevice.MODE_P2P_TARGET) { + if (device.doConnect()) { + /* Check Llcp compliancy */ + if (doCheckLlcp()) { + /* Activate Llcp Link */ + if (doActivateLlcp()) { + Log.d(TAG, "Initiator Activate LLCP OK"); + /* Broadcast Intent Link LLCP activated */ + Intent LlcpLinkIntent = new Intent(); + LlcpLinkIntent + .setAction(INTERNAL_LLCP_LINK_STATE_CHANGED_ACTION); + LlcpLinkIntent.putExtra( + INTERNAL_LLCP_LINK_STATE_CHANGED_EXTRA, + NfcManager.LLCP_LINK_STATE_ACTIVATED); + Log.d(TAG, "Broadcasting internal LLCP activation"); + mContext.sendBroadcast(LlcpLinkIntent); + } + + } else { + device.doDisconnect(); + } + + } + + } else if (device.getMode() == NativeP2pDevice.MODE_P2P_INITIATOR) { + /* Check Llcp compliancy */ + if (doCheckLlcp()) { + /* Activate Llcp Link */ + if (doActivateLlcp()) { + Log.d(TAG, "Target Activate LLCP OK"); + /* Broadcast Intent Link LLCP activated */ + Intent LlcpLinkIntent = new Intent(); + LlcpLinkIntent + .setAction(INTERNAL_LLCP_LINK_STATE_CHANGED_ACTION); + LlcpLinkIntent.putExtra(INTERNAL_LLCP_LINK_STATE_CHANGED_EXTRA, + NfcManager.LLCP_LINK_STATE_ACTIVATED); + Log.d(TAG, "Broadcasting internal LLCP activation"); + mContext.sendBroadcast(LlcpLinkIntent); + } + } + } + break; + + case MSG_LLCP_LINK_DEACTIVATED: + /* Broadcast Intent Link LLCP activated */ + Log.d(TAG, "LLCP Link Deactivated message"); + Intent LlcpLinkIntent = new Intent(); + LlcpLinkIntent.setAction(NfcManager.LLCP_LINK_STATE_CHANGED_ACTION); + LlcpLinkIntent.putExtra(NfcManager.LLCP_LINK_STATE_CHANGED_EXTRA, + NfcManager.LLCP_LINK_STATE_DEACTIVATED); + Log.d(TAG, "Broadcasting LLCP deactivation"); + mContext.sendOrderedBroadcast(LlcpLinkIntent, + android.Manifest.permission.NFC_LLCP); + break; + + case MSG_TARGET_DESELECTED: + /* Broadcast Intent Target Deselected */ + Log.d(TAG, "Target Deselected"); + Intent TargetDeselectedIntent = new Intent(); + TargetDeselectedIntent.setAction(INTERNAL_TARGET_DESELECTED_ACTION); + Log.d(TAG, "Broadcasting Intent"); + mContext.sendOrderedBroadcast(TargetDeselectedIntent, + android.Manifest.permission.NFC_LLCP); + break; + + default: + Log.e(TAG, "Unknown message received"); + break; + } + } catch (Exception e) { + // Log, don't crash! + Log.e(TAG, "Exception in NfcHandler.handleMessage:", e); + } + } + }; + + /** + * Notifies Ndef Message + */ + private void notifyNdefMessageListeners(NativeNfcTag tag) { + Message msg = mNfcHandler.obtainMessage(); + + msg.what = MSG_NDEF_TAG; + msg.obj = tag; + + mNfcHandler.sendMessage(msg); + } + + /** + * Notifies transaction + */ + private void notifyTargetDeselected() { + Message msg = mNfcHandler.obtainMessage(); + + msg.what = MSG_TARGET_DESELECTED; + + mNfcHandler.sendMessage(msg); + } + + /** + * Notifies transaction + */ + private void notifyTransactionListeners(byte[] aid) { + Message msg = mNfcHandler.obtainMessage(); + + msg.what = MSG_CARD_EMULATION; + msg.obj = aid; + + mNfcHandler.sendMessage(msg); + } + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + private void notifyLlcpLinkActivation(NativeP2pDevice device) { + Message msg = mNfcHandler.obtainMessage(); + + msg.what = MSG_LLCP_LINK_ACTIVATION; + msg.obj = device; + + mNfcHandler.sendMessage(msg); + } + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + private void notifyLlcpLinkDeactivated() { + Message msg = mNfcHandler.obtainMessage(); + + msg.what = MSG_LLCP_LINK_DEACTIVATED; + + mNfcHandler.sendMessage(msg); + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/NativeNfcTag.java b/core/java/com/trustedlogic/trustednfc/android/internal/NativeNfcTag.java new file mode 100644 index 0000000..b92783d --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/NativeNfcTag.java @@ -0,0 +1,62 @@ +/* + * 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. + */ + +/** + * File : NativeNfcTag.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +/** + * Native interface to the NFC tag functions + * + * {@hide} + */ +public class NativeNfcTag { + private int mHandle; + + private String mType; + + private byte[] mUid; + + public native boolean doConnect(); + + public native boolean doDisconnect(); + + public native void doAsyncDisconnect(); + + public native byte[] doTransceive(byte[] data); + + public native boolean checkNDEF(); + + public native byte[] doRead(); + + public native boolean doWrite(byte[] buf); + + public int getHandle() { + return mHandle; + } + + public String getType() { + return mType; + } + + public byte[] getUid() { + return mUid; + } +} diff --git a/core/java/com/trustedlogic/trustednfc/android/internal/NativeP2pDevice.java b/core/java/com/trustedlogic/trustednfc/android/internal/NativeP2pDevice.java new file mode 100644 index 0000000..75d25ba --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/internal/NativeP2pDevice.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +/** + * File : NativeP2pDevice.java + * Original-Author : Trusted Logic S.A. (Sylvain Fonteneau) + * Created : 18-02-2010 + */ + +package com.trustedlogic.trustednfc.android.internal; + +/** + * Native interface to the P2P Initiator functions + * + * {@hide} + */ +public class NativeP2pDevice { + + /** + * Peer-to-Peer Target. + */ + public static final short MODE_P2P_TARGET = 0x00; + + /** + * Peer-to-Peer Initiator. + */ + public static final short MODE_P2P_INITIATOR = 0x01; + + /** + * Invalid target type. + */ + public static final short MODE_INVALID = 0xff; + + private int mHandle; + + private int mMode; + + private byte[] mGeneralBytes; + + public native byte[] doReceive(); + + public native boolean doSend(byte[] data); + + public native boolean doConnect(); + + public native boolean doDisconnect(); + + public native byte[] doTransceive(byte[] data); + + public int getHandle() { + return mHandle; + } + + public int getMode() { + return mMode; + } + + public byte[] getGeneralBytes() { + return mGeneralBytes; + } + +} diff --git a/core/java/com/trustedlogic/trustednfc/android/package.html b/core/java/com/trustedlogic/trustednfc/android/package.html new file mode 100644 index 0000000..0c0b605 --- /dev/null +++ b/core/java/com/trustedlogic/trustednfc/android/package.html @@ -0,0 +1,473 @@ +<html> +<body> + +<p>Provides classes that manage the NFC functionality.</p> + +<p>The NFC functionality is related to Near Field Communication.</p> + +<p>The NFC APIs let applications:</p> +<ul> + <li>Scan for remote NFC targets (NFC Tag or NFC Peer)</li> + <li>Transfer raw data to and from remote NFC targets (NFC Tags or NFC Peer)</li> + <li>Read/Write NDEF data from/to remote NFC targets (NFC Tags)</li> + <li>Establish LLCP connection with a remote NFC target (NFC Peer with LLCP support)</li> + <li>Exchange data with a remote NFC target through LLCP services (NFC Peer with LLCP support)</li> + <li>Be notified of transactions on the local Secure Element by an external NFC reader</li> +</ul> + + +<h1>Setting Up NFC</h1> + +<p> +Before an application can use the NFC feature, it needs to check if NFC is +supported on the device by getting an instance of the +{@link com.trustedlogic.trustednfc.android.NfcManager} class. +</p> + +<pre> + NfcManager mNfcManager = (NfcManager) getSystemService(Context.NFC_SERVICE); + if (mNfcManager == null) { + // Device does not support NFC + } +</pre> + +<p> +An application can ensure that NFC is enabled. +If not, an application with the needed permission can request that NFC be +enabled. +</p> + +<pre> + if (!mNfcManager.isEnabled) { + // NFC is currently disabled. + // Enable NFC. + mNfcManager.enable(); + } +</pre> + +<p> +Before using the card emulation mode, an application can ensure that a secure +element is selected ({@link com.trustedlogic.trustednfc.android.NfcManager#getSelectedSecureElement}). +If not, an application with the needed permission can recover the list of +available secure elements on the device +({@link com.trustedlogic.trustednfc.android.NfcManager#getSecureElementList}) and select one +({@link com.trustedlogic.trustednfc.android.NfcManager#selectSecureElement}). +</p> + +<p> +Before using the NFC feature, an application can configure the NFC device by +calling {@link com.trustedlogic.trustednfc.android.NfcManager#setProperties}. This function allows: +</p> +<ul> + <li>Enabling/disabling the NFC device capabilities (RF types, baudrates, + NFCIP-1 mode and role...)</li> + <li>Settings the NFCIP-1 general bytes and the LLCP link parameters</li> +</ul> +<p> +The setting properties can be customized according to the Device capabilities. +The next table give the minimal set of properties supported by the Device. +Depending on the implementation, the table may be completed. +</p> +<table> + <TR><TH> Property Name </TH><TH> Property Values </TH></TR> + <TR><TD> discovery.felica </TD><TD> <b>true</b>|false </TD></TR> + <TR><TD> discovery.iso14443A </TD><TD> <b>true</b>|false </TD></TR> + <TR><TD> discovery.iso14443B </TD><TD> <b>true</b>|false </TD></TR> + <TR><TD> discovery.iso15693 </TD><TD> <b>true</b>|false </TD></TR> + <TR><TD> discovery.nfcip </TD><TD> <b>true</b>|false </TD></TR> + <TR><TD> nfcip.baudrate </TD><TD> 106|212|424 </TD></TR> + <TR><TD> nfcip.generalbytes </TD><TD> </TD></TR> + <TR><TD> nfcip.mode </TD><TD> active|passive|<b>all</b> </TD></TR> + <TR><TD> nfcip.role </TD><TD> initiator|target|<b>both</b> </TD></TR> + <TR><TD> llcp.lto </TD><TD> <b>150</b> (0 to 255) </TD></TR> + <TR><TD> llcp.opt </TD><TD> <b>0</b> (0 to 3) </TD></TR> + <TR><TD> llcp.miu </TD><TD> <b>128</b> (128 to 2176) </TD></TR> + <TR><TD> llcp.wks </TD><TD> <b>1</b> (0 to 15) </TD></TR> +</table> +<p>(default values in bold)</p> + + +<h1>NFC Permissions</h1> + +<p> +To change the NFC service settings such as enabling the NFC targets +discovery or activating the secure element, an application must declare the +NFC_ADMIN permission. +</p> +<p> +To perform NFC raw communication with a remote NFC target in +Reader/Write Mode or Peer-to-Peer Mode, an application must declare the NFC_RAW +permission. +</p> +<p> +To receive NDEF message or Secure Element intents, an application must declare +the NFC_NOTIFY permission. +</p> +<p> +To receive the LLCP link intent and perform an LLCP communication with a remote NFC target, an application must +declare the NFC_LLCP permission. +</p> + + +<h1>NFC Usage</h1> + +<p> +The following code samples illustrate the APIs usage regarding the NFC service +use cases. +</p> + +<h2>Reader/Writer Mode NDEF message notification</h2> + +<p> +This code sample illustrates the NDEF message notification through an Intent declared in the manifest and a receiver implemented in the application. +</p> +<p>Main involved classes/methods:</p> + +<p>Manifest Example:</p> +<pre> + <receiver android:name=".NfcReaderDemoReceiver"> + <intent-filter> + <action android:name= "com.trustedlogic.trustednfc.android.action.NDEF_TAG_DISCOVERED"/> + </intent-filter> + </receiver> +</pre> + +<p>Receiver Example:</p> +<ul> + <li>{@link com.trustedlogic.trustednfc.android.NdefMessage}</li> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#NDEF_TAG_DISCOVERED_ACTION}</li> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#NDEF_MESSAGE_EXTRA}</li> +</ul> +<pre> +public class NdefMessageReceiverSample extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(NfcManager.NDEF_TAG_DISCOVERERD_ACTION)) { + NdefMessage msg = intent.getParcelableExtra(NfcManager.NDEF_MESSAGE_EXTRA); + + /* Manage the NdefMessage received */ + } +</pre> + +<h2>Reader/Writer Mode raw exchange</h2> + +<p> +This code sample illustrates raw exchanges with a NFC target in Reader/Writer +mode. +</p> +<p>Main involved classes/methods:</p> +<ul> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#openTagConnection}</li> + <li>{@link com.trustedlogic.trustednfc.android.NfcTag}</li> +</ul> + +<pre> +public class TagReaderSample { + + /** The NFC manager to access NFC features */ + private NfcManager manager = (NfcManager) getSystemService(Context.NFC_SERVICE); + + private void runTagReader() { + NfcTag tag = null; + String type; + byte[] cmd = { 0x01, 0x02, 0x03 }; + byte[] res; + + while (true) { + try { + Log.i("NFC example", "Please wave in front of the tag"); + // Open a connection on next available tag + try { + tag = manager.openTagConnection(); + } catch (NfcException e) { + // TODO: Handle open failure + } + + // Look for a mifare 4k + type = tag.getType(); + if (type.equals("Mifare4K")) { + Log.i("NFC example", "Tag detected"); + tag.connect(); + // Ready to communicate, we can send transceive ! + res = tag.transceive(cmd); + } else { + Log.i("NFC example", "Unknown tag"); + } + } catch (IOException e) { + // TODO: Handle broken connection + } finally { + if (tag != null) { + tag.close(); + } + } + } + } +} +</pre> + +<h2>Peer-to-Peer Mode raw exchange</h2> + +<p> +This code sample illustrates raw exchanges with a NFC target in Peer-to-Peer +mode. +</p> +<p>Main involved classes/methods:</p> +<ul> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#openP2pConnection}</li> + <li>{@link com.trustedlogic.trustednfc.android.P2pDevice}</li> + <li>{@link com.trustedlogic.trustednfc.android.P2pInitiator}</li> + <li>{@link com.trustedlogic.trustednfc.android.P2pTarget}</li> +</ul> + +<pre> +public class P2pSample { + + /** The NFC manager to access NFC features */ + private NfcManager manager = (NfcManager) getSystemService(Context.NFC_SERVICE); + + private void runP2p() { + P2pDevice deviceP2p; + P2pInitiator initiator; + P2pTarget target; + byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + byte[] echo = new byte[data.length * 10]; + + try { + deviceP2p = manager.openP2pConnection(); + + if (deviceP2p.getMode() == P2pDevice.MODE_P2P_INITIATOR) { + target = new P2pTarget(deviceP2p); + // Connect to the detected P2P target + target.connect(); + // send data to the target + target.transceive(data); + // disconnect the connected target + target.disconnect(); + } else if (deviceP2p.getMode() == P2pDevice.MODE_P2P_TARGET) { + initiator = new P2pInitiator(deviceP2p); + //target in receive state + echo = initiator.receive(); + // send back the data received + initiator.send(echo); + } + } catch (IOException e0) { + + } catch (NfcException e1) { + + } + } +} +</pre> + +<h2>Peer-to-Peer Mode LLCP exchange</h2> + +<p> +This code sample illustrates how to get LLCP link state notification with the declaration of a Receiver in the manifest of the application and the implementation +of the receiver in the application. +</p> +<p>Manifest Example:</p> +<pre> + <receiver android:name=".LlcpModeReceiverSample"> + <intent-filter> + <action android:name= "com.trustedlogic.trustednfc.android.action.LLCP_LINK_STATE_CHANGED"/> + </intent-filter> + </receiver> +</pre> + +<p>Receiver Example:</p> +<ul> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#LLCP_LINK_STATE_CHANGED_ACTION}</li> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#LLCP_LINK_STATE_CHANGED_EXTRA}</li> +</ul> +<pre> +public class LlcpModeReceiverSample extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + + if (intent.getAction().equals(NfcManager.LLCP_LINK_STATE_CHANGED_ACTION)){ + byte[] aid = intent.getByteArrayExtra(NfcManager.LLCP_LINK_STATE_CHANGED_EXTRA); + /* Create an LLCP service or client and start an LLCP communication */ + } + } +</pre> + + +<p> +This code samples illustrate LLCP exchanges with a NFC Peer. +</p> +<p>Main involved classes/methods:</p> +<ul> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#createLlcpSocket}</li> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#createLlcpConnectionlessSocket}</li> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#createLlcpServiceSocket}</li> + <li>{@link com.trustedlogic.trustednfc.android.LlcpSocket}</li> + <li>{@link com.trustedlogic.trustednfc.android.LlcpConnectionlessSocket}</li> + <li>{@link com.trustedlogic.trustednfc.android.LlcpPacket}</li> + <li>{@link com.trustedlogic.trustednfc.android.LlcpServiceSocket}</li> +</ul> + +<pre> +public class LlcpServerSample { + + /** The NFC manager to access NFC features */ + private NfcManager manager = (NfcManager) getSystemService(Context.NFC_SERVICE); + + private void runLlcpClient() { + LlcpSocket sock; + byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + byte[] echo = new byte[data.length * 10]; + int length = 0; + + sock = manager.createLlcpSocket((short) 128, (byte) 2, 1024); + + // set a timeout in ms for connect request + sock.setConnectTimeout(10); + + try { + // Connect to remote service + // NOTE: could be sock.connect("com.trusted-logic.tnfc.testapp"); + sock.connect((byte) 0x10); + + // Send data + for (int i = 0; i < 10; i++) { + sock.send(data); + } + + // Receive echo + while (length < 10 * data.length) { + length += sock.receive(echo); + } + + } catch (IOException e) { + // TODO: Handle broken connection broken (link down, remote closure + // or connect rejected) or Timeout expired + } + } +} +</pre> + +<pre> +public class LlcpClientSample { + + /** The NFC manager to access NFC features */ + private NfcManager manager = (NfcManager) getSystemService(Context.NFC_SERVICE); + + private void runLlcpClient() { + LlcpSocket sock; + byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + byte[] echo = new byte[data.length * 10]; + int length = 0; + + sock = manager.createLlcpSocket((short) 128, (byte) 2, 1024); + try { + // Connect to remote service + // NOTE: could be sock.connect("com.trusted-logic.tnfc.testapp"); + sock.connect((byte) 0x10); + + // Send data + for (int i = 0; i < 10; i++) { + sock.send(data); + } + + // Receive echo + while (length < 10 * data.length) { + length += sock.receive(echo); + } + + } catch (IOException e) { + // TODO: Handle broken connection broken (link down, remote closure + // or connect rejected) + } + } +} +</pre> + +<h2>Card Emulation Mode transaction notification</h2> + +<p> +This code sample illustrates how to get the card emulation notification with the declaration of a Receiver in the manifest of the application and the implementation +of the receiver in the application. +</p> +<p>Manifest Example:</p> +<pre> + <receiver android:name=".NfcReaderDemoReceiver"> + <intent-filter> + <action android:name= "com.trustedlogic.trustednfc.android.action.TRANSACTION_DETECTED"/> + </intent-filter> + </receiver> +</pre> + +<p>Receiver Example:</p> +<ul> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#TRANSACTION_DETECTED_ACTION}</li> + <li>{@link com.trustedlogic.trustednfc.android.NfcManager#AID_EXTRA}</li> +</ul> +<pre> +public class CardEmulationReceiverSample extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + + if (intent.getAction().equals(NfcManager.TRANSACTION_DETECTED_ACTION)){ + byte[] aid = intent.getByteArrayExtra(NfcManager.AID_EXTRA); + /* Manage the AID: */ + /* For example start an activity related to this AID value or display a popup with the AID */ + } + } +</pre> + + + +<h1>Multiple Applications rules</h1> + +<p> +Several LLCP sockets can be created by a single application or by multiple +applications by calling {@link com.trustedlogic.trustednfc.android.NfcManager#createLlcpSocket}, +{@link com.trustedlogic.trustednfc.android.NfcManager#createLlcpConnectionlessSocket} or +{@link com.trustedlogic.trustednfc.android.NfcManager#createLlcpServiceSocket}, provided the local SAP +numbers are differents. +</p> + +<p> +Only one application can open a raw connection by calling +{@link com.trustedlogic.trustednfc.android.NfcManager#openTagConnection} or +{@link com.trustedlogic.trustednfc.android.NfcManager#openP2pConnection}. +While this application has not closed or cancelled its connection, any other +application that attempts to open another raw connection will raise an +exception. +During an open connnection, the card emulation mode is always enabled and +applications are able to receive card emulation intents. +</p> + +<p> +When an application opens a tag connection by calling +{@link com.trustedlogic.trustednfc.android.NfcManager#openTagConnection}, this operation is exclusive, no NDEF message intent are +broadcast while the connection is not closed or canceled. +</p> + +<p> +When an application opens a peer-to-peer connection by calling +{@link com.trustedlogic.trustednfc.android.NfcManager#openP2pConnection}, this operation is exclusive, no LLCP intent are broadcast and LLCP sockets are +disabled while the connection is not closed or canceled. +</p> + + +<h1>NFC Tag types</h1> + +<p> +The {@link com.trustedlogic.trustednfc.android.NfcTag} type returned by +{@link com.trustedlogic.trustednfc.android.NfcTag#getType} indicates the set of +commands supported by the tag. These commands can be used in +{@link com.trustedlogic.trustednfc.android.NfcTag#transceive}. +</p> + +<TABLE BORDER="1"> + <TR><TH> Tag Type </TH><TH> Returned string </TH></TR> + <TR><TD> Jewel/Topaz </TD><TD> Jewel </TD></TR> + <TR><TD> Mifare UltraLight </TD><TD> MifareUL </TD></TR> + <TR><TD> Mifare Standard 1K </TD><TD> Mifare1K </TD></TR> + <TR><TD> Mifare Standard 4K </TD><TD> Mifare4K </TD></TR> + <TR><TD> Mifare DESFIRE </TD><TD> MifareDESFIRE </TD></TR> + <TR><TD> Felica </TD><TD> Felica </TD></TR> + <TR><TD> ISO14443-4 A or B </TD><TD> Iso14443 </TD></TR> + <TR><TD> ISO15693 </TD><TD> Iso15693 </TD></TR> +</TABLE> + +</body> +</html> diff --git a/core/jni/Android.mk b/core/jni/Android.mk index fffd185..64a2331 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -202,6 +202,14 @@ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui endif +ifeq ($(BOARD_HAVE_NFC),true) +LOCAL_SHARED_LIBRARIES += \ + libnfc_jni \ + libnfc + +LOCAL_CFLAGS += -DHAVE_NFC +endif + ifeq ($(BOARD_HAVE_BLUETOOTH),true) LOCAL_C_INCLUDES += \ external/dbus \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index ff62e0d..a4a1a70 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -170,6 +170,18 @@ extern int register_android_view_MotionEvent(JNIEnv* env); extern int register_android_content_res_ObbScanner(JNIEnv* env); extern int register_android_content_res_Configuration(JNIEnv* env); +#ifdef HAVE_NFC +extern int register_com_trustedlogic_trustednfc_android_internal_NativeNfcManager(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_internal_NativeNfcTag(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_internal_NativeNdefTag(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_NdefMessage(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_NdefRecord(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_internal_NativeP2pDevice(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_internal_NativeLlcpSocket(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_internal_NativeLlcpConnectionlessSocket(JNIEnv *env); +extern int register_com_trustedlogic_trustednfc_android_internal_NativeLlcpServiceSocket(JNIEnv *env); +#endif + static AndroidRuntime* gCurRuntime = NULL; static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL) @@ -1287,6 +1299,18 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_res_ObbScanner), REG_JNI(register_android_content_res_Configuration), + +#ifdef HAVE_NFC + REG_JNI(register_com_trustedlogic_trustednfc_android_internal_NativeNfcManager), + REG_JNI(register_com_trustedlogic_trustednfc_android_internal_NativeNfcTag), + REG_JNI(register_com_trustedlogic_trustednfc_android_internal_NativeNdefTag), + REG_JNI(register_com_trustedlogic_trustednfc_android_NdefMessage), + REG_JNI(register_com_trustedlogic_trustednfc_android_NdefRecord), + REG_JNI(register_com_trustedlogic_trustednfc_android_internal_NativeP2pDevice), + REG_JNI(register_com_trustedlogic_trustednfc_android_internal_NativeLlcpSocket), + REG_JNI(register_com_trustedlogic_trustednfc_android_internal_NativeLlcpConnectionlessSocket), + REG_JNI(register_com_trustedlogic_trustednfc_android_internal_NativeLlcpServiceSocket), +#endif }; /* diff --git a/core/jni/android_content_res_ObbScanner.cpp b/core/jni/android_content_res_ObbScanner.cpp index 62c89fc..2a9eacf 100644 --- a/core/jni/android_content_res_ObbScanner.cpp +++ b/core/jni/android_content_res_ObbScanner.cpp @@ -34,7 +34,17 @@ static struct { jfieldID flags; } gObbInfoClassInfo; -static jboolean android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject clazz, jstring file, +static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL) +{ + jclass npeClazz; + + npeClazz = env->FindClass(exc); + LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc); + + env->ThrowNew(npeClazz, msg); +} + +static void android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject clazz, jstring file, jobject obbInfo) { const char* filePath = env->GetStringUTFChars(file, JNI_FALSE); @@ -42,7 +52,8 @@ static jboolean android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject c sp<ObbFile> obb = new ObbFile(); if (!obb->readFrom(filePath)) { env->ReleaseStringUTFChars(file, filePath); - return JNI_FALSE; + doThrow(env, "java/io/IOException", "Could not read OBB file"); + return; } env->ReleaseStringUTFChars(file, filePath); @@ -51,13 +62,13 @@ static jboolean android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject c jstring packageName = env->NewStringUTF(packageNameStr); if (packageName == NULL) { - return JNI_FALSE; + doThrow(env, "java/io/IOException", "Could not read OBB file"); + return; } env->SetObjectField(obbInfo, gObbInfoClassInfo.packageName, packageName); env->SetIntField(obbInfo, gObbInfoClassInfo.version, obb->getVersion()); - - return JNI_TRUE; + env->SetIntField(obbInfo, gObbInfoClassInfo.flags, obb->getFlags()); } /* @@ -65,7 +76,7 @@ static jboolean android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject c */ static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - { "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)Z", + { "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)V", (void*) android_content_res_ObbScanner_getObbInfo }, }; diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp index d930065..05e1ff3 100644 --- a/core/jni/android_database_SQLiteDatabase.cpp +++ b/core/jni/android_database_SQLiteDatabase.cpp @@ -91,7 +91,7 @@ static void registerLoggingFunc(const char *path) { LOGV("Registering sqlite logging func \n"); int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path, 0)); if (err != SQLITE_OK) { - LOGE("sqlite_config failed error_code = %d. THIS SHOULD NEVER occur.\n", err); + LOGW("sqlite returned error = %d when trying to register logging func.\n", err); return; } loggingFuncSet = true; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d5065f6..3a1d76f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -88,6 +88,10 @@ <protected-broadcast android:name="android.hardware.action.USB_CAMERA_ATTACHED" /> <protected-broadcast android:name="android.hardware.action.USB_CAMERA_DETACHED" /> + <protected-broadcast android:name="com.trustedlogic.trustednfc.android.action.NDEF_TAG_DISCOVERED" /> + <protected-broadcast android:name="com.trustedlogic.trustednfc.android.action.TRANSACTION_DETECTED" /> + <protected-broadcast android:name="com.trustedlogic.trustednfc.android.action.LLCP_LINK_STATE_CHANGED" /> + <!-- ====================================== --> <!-- Permissions for things that cost money --> <!-- ====================================== --> @@ -338,6 +342,30 @@ android:description="@string/permdesc_bluetooth" android:label="@string/permlab_bluetooth" /> + <!-- Allows applications to access remote NFC devices + @hide --> + <permission android:name="com.trustedlogic.trustednfc.permission.NFC_RAW" + android:permissionGroup="android.permission-group.NETWORK" + android:protectionLevel="dangerous" + android:description="@string/permdesc_nfcRaw" + android:label="@string/permlab_nfcRaw" /> + + <!-- Allows applications to be notified of remote NFC devices + @hide --> + <permission android:name="com.trustedlogic.trustednfc.permission.NFC_NOTIFY" + android:permissionGroup="android.permission-group.NETWORK" + android:protectionLevel="dangerous" + android:description="@string/permdesc_nfcNotify" + android:label="@string/permlab_nfcNotify" /> + + <!-- Allows applications to be notified of remote NFC LLCP devices + @hide --> + <permission android:name="com.trustedlogic.trustednfc.permission.NFC_LLCP" + android:permissionGroup="android.permission-group.NETWORK" + android:protectionLevel="dangerous" + android:description="@string/permdesc_nfcLlcp" + android:label="@string/permlab_nfcLlcp" /> + <!-- Allows applications to call into AccountAuthenticators. Only the system can get this permission. --> <permission android:name="android.permission.ACCOUNT_MANAGER" @@ -848,6 +876,14 @@ android:description="@string/permdesc_bluetoothAdmin" android:label="@string/permlab_bluetoothAdmin" /> + <!-- Allows applications to change NFC connectivity settings + @hide --> + <permission android:name="com.trustedlogic.trustednfc.permission.NFC_ADMIN" + android:permissionGroup="android.permission-group.SYSTEM_TOOLS" + android:protectionLevel="dangerous" + android:description="@string/permdesc_nfcAdmin" + android:label="@string/permlab_nfcAdmin" /> + <!-- Allows an application to clear the caches of all installed applications on the device. --> <permission android:name="android.permission.CLEAR_APP_CACHE" diff --git a/core/res/res/color/primary_text_disable_only_holo_dark.xml b/core/res/res/color/primary_text_disable_only_holo_dark.xml new file mode 100644 index 0000000..6de4583 --- /dev/null +++ b/core/res/res/color/primary_text_disable_only_holo_dark.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/> + <item android:color="@android:color/bright_foreground_dark"/> +</selector> + diff --git a/core/res/res/color/primary_text_disable_only_holo_light.xml b/core/res/res/color/primary_text_disable_only_holo_light.xml new file mode 100644 index 0000000..87a92c8 --- /dev/null +++ b/core/res/res/color/primary_text_disable_only_holo_light.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/> + <item android:color="@android:color/bright_foreground_light"/> +</selector> + diff --git a/core/res/res/color/primary_text_focused_holo_dark.xml b/core/res/res/color/primary_text_focused_holo_dark.xml new file mode 100644 index 0000000..80c68a4 --- /dev/null +++ b/core/res/res/color/primary_text_focused_holo_dark.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse" /> + <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse" /> + <item android:state_focused="true" android:color="@android:color/bright_foreground_dark_inverse" /> + <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark_inverse" /> + <item android:color="@android:color/bright_foreground_dark" /> +</selector> + diff --git a/core/res/res/color/primary_text_holo_dark.xml b/core/res/res/color/primary_text_holo_dark.xml new file mode 100644 index 0000000..69ee309 --- /dev/null +++ b/core/res/res/color/primary_text_holo_dark.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/> + <item android:state_window_focused="false" android:color="@android:color/bright_foreground_dark"/> + <item android:state_pressed="true" android:color="@android:color/bright_foreground_dark"/> + <item android:state_selected="true" android:color="@android:color/bright_foreground_dark"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_dark"/> + <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected --> +</selector> diff --git a/core/res/res/color/primary_text_holo_light.xml b/core/res/res/color/primary_text_holo_light.xml new file mode 100644 index 0000000..a8d31ce --- /dev/null +++ b/core/res/res/color/primary_text_holo_light.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/> + <item android:state_window_focused="false" android:color="@android:color/bright_foreground_light"/> + <item android:state_pressed="true" android:color="@android:color/bright_foreground_light"/> + <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/> + <item android:color="@android:color/bright_foreground_light"/> <!-- not selected --> + +</selector> + diff --git a/core/res/res/color/primary_text_nodisable_holo_dark.xml b/core/res/res/color/primary_text_nodisable_holo_dark.xml new file mode 100644 index 0000000..f45088f --- /dev/null +++ b/core/res/res/color/primary_text_nodisable_holo_dark.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/> + <item android:color="@android:color/bright_foreground_dark"/> <!-- not selected --> +</selector> + diff --git a/core/res/res/color/primary_text_nodisable_holo_light.xml b/core/res/res/color/primary_text_nodisable_holo_light.xml new file mode 100644 index 0000000..331e8c3 --- /dev/null +++ b/core/res/res/color/primary_text_nodisable_holo_light.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="@android:color/bright_foreground_light"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/> + <item android:color="@android:color/bright_foreground_light"/> <!-- not selected --> +</selector> + diff --git a/core/res/res/color/search_url_text_holo.xml b/core/res/res/color/search_url_text_holo.xml new file mode 100644 index 0000000..78093ba --- /dev/null +++ b/core/res/res/color/search_url_text_holo.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:color="@android:color/search_url_text_pressed"/> + <item android:state_selected="true" android:color="@android:color/search_url_text_selected"/> + <item android:color="@android:color/search_url_text_normal"/> <!-- not selected --> +</selector> diff --git a/core/res/res/color/secondary_text_holo_dark.xml b/core/res/res/color/secondary_text_holo_dark.xml new file mode 100644 index 0000000..376156e --- /dev/null +++ b/core/res/res/color/secondary_text_holo_dark.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/> + <item android:state_window_focused="false" android:color="@android:color/dim_foreground_dark"/> + <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/> + <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/> + <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/> + <item android:state_pressed="true" android:color="@android:color/dim_foreground_dark_inverse"/> + <item android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/> + <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected --> +</selector> diff --git a/core/res/res/color/secondary_text_holo_light.xml b/core/res/res/color/secondary_text_holo_light.xml new file mode 100644 index 0000000..b791aeb --- /dev/null +++ b/core/res/res/color/secondary_text_holo_light.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/> + <item android:state_window_focused="false" android:color="@android:color/dim_foreground_light"/> + <!-- Since there is only one selector (for both light and dark), the light's selected state shouldn't be inversed like the dark's. --> + <item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/> + <item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/> + <item android:state_pressed="true" android:color="@android:color/dim_foreground_light"/> + <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_light"/> + <item android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/> + <item android:color="@android:color/dim_foreground_light"/> <!-- not selected --> +</selector> diff --git a/core/res/res/color/secondary_text_nodisable_holo_dark.xml b/core/res/res/color/secondary_text_nodisable_holo_dark.xml new file mode 100644 index 0000000..8c22241 --- /dev/null +++ b/core/res/res/color/secondary_text_nodisable_holo_dark.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/> + <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected --> +</selector> diff --git a/core/res/res/color/secondary_text_nodisable_holo_light.xml b/core/res/res/color/secondary_text_nodisable_holo_light.xml new file mode 100644 index 0000000..8c22241 --- /dev/null +++ b/core/res/res/color/secondary_text_nodisable_holo_light.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="@android:color/dim_foreground_dark_inverse"/> + <item android:state_activated="true" android:color="@android:color/bright_foreground_dark_inverse"/> + <item android:color="@android:color/dim_foreground_dark"/> <!-- not selected --> +</selector> diff --git a/core/res/res/color/tertiary_text_holo_dark.xml b/core/res/res/color/tertiary_text_holo_dark.xml new file mode 100644 index 0000000..269ff71 --- /dev/null +++ b/core/res/res/color/tertiary_text_holo_dark.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="#808080"/> + <item android:state_window_focused="false" android:color="#808080"/> + <item android:state_pressed="true" android:color="#808080"/> + <item android:state_selected="true" android:color="@android:color/dim_foreground_light"/> + <item android:color="#808080"/> <!-- not selected --> +</selector> + diff --git a/core/res/res/color/tertiary_text_holo_light.xml b/core/res/res/color/tertiary_text_holo_light.xml new file mode 100644 index 0000000..9e5c2d9 --- /dev/null +++ b/core/res/res/color/tertiary_text_holo_light.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:color="#808080"/> + <item android:state_window_focused="false" android:color="#808080"/> + <item android:state_pressed="true" android:color="#808080"/> + <item android:state_selected="true" android:color="#808080"/> + <item android:color="#808080"/> <!-- not selected --> +</selector> + diff --git a/core/res/res/color/widget_edittext_holo_dark.xml b/core/res/res/color/widget_edittext_holo_dark.xml new file mode 100644 index 0000000..4f3eb62 --- /dev/null +++ b/core/res/res/color/widget_edittext_holo_dark.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="false" android:color="@android:color/bright_foreground_light"/> <!-- unfocused --> + <item android:color="@android:color/bright_foreground_light"/> +</selector> diff --git a/core/res/res/color/widget_edittext_holo_light.xml b/core/res/res/color/widget_edittext_holo_light.xml new file mode 100644 index 0000000..7b950d4 --- /dev/null +++ b/core/res/res/color/widget_edittext_holo_light.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="false" android:color="@android:color/bright_foreground_dark"/> <!-- unfocused --> + <item android:color="@android:color/bright_foreground_dark"/> +</selector> diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_dark.png Binary files differindex d72e2b9..d93e580 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_light.png Binary files differindex 240a044..ffbe776 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_dark.png Binary files differindex d72e2b9..d93e580 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_disable_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_light.png Binary files differindex 240a044..ffbe776 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_disable_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_off_disable_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_holo_dark.png Binary files differindex 911e1aa..4148ac4 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_holo_light.png Binary files differindex 4ca3c56..2cc946f 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_dark.png Binary files differindex 08f4181..9f9cb01 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_light.png Binary files differindex d3754dd..56425d2 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_off_pressed_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_dark.png Binary files differindex 264f102..f819928 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_selected_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_light.png Binary files differindex 48506bf..57cb512 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_selected_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_off_selected_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png Binary files differindex 7805458..1f7aeee 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_light.png Binary files differindex 4e268d5..1f740ad 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_dark.png Binary files differindex 7805458..1f7aeee 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_disable_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_light.png Binary files differindex 4e268d5..1f740ad 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_disable_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_on_disable_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_holo_dark.png Binary files differindex 5541c67..d0c4415 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_holo_light.png Binary files differindex 768c4af..af84d4b 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_dark.png Binary files differindex 37e3953..91e5f14 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_light.png Binary files differindex fc29e46..0cf7ed2 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_on_pressed_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_dark.png Binary files differindex a0beac4..e758aab 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_selected_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_light.png Binary files differindex 5df45c7..2edf656 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_selected_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_check_on_selected_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..3deb385 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png Binary files differnew file mode 100644 index 0000000..de378a5 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..35f8b3d --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png Binary files differnew file mode 100644 index 0000000..3f45375 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_disabled_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..ea58bf7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png Binary files differnew file mode 100644 index 0000000..b225aaf --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..b5b1533 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_normal_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png Binary files differnew file mode 100644 index 0000000..81b8a75 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_normal_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..eb8d85a --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png Binary files differnew file mode 100644 index 0000000..6777ebf --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_default_pressed_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..2a7505b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_light.png Binary files differnew file mode 100644 index 0000000..bbb01f0 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_disabled_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..b617a2a --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_light.png Binary files differnew file mode 100644 index 0000000..fd59f4a --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_disabled_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..5d17cde --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_light.png Binary files differnew file mode 100644 index 0000000..b6b4bf1 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_focused_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..eab5039 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_light.png Binary files differnew file mode 100644 index 0000000..b6b4bf1 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_focused_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..edf2296 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_light.png Binary files differnew file mode 100644 index 0000000..68afa4c --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_normal_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..c7df168 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_light.png Binary files differnew file mode 100644 index 0000000..5a9087b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_normal_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_off_holo_dark.png Binary files differindex 301c97d..dd18b7a 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_off_holo_light.png Binary files differindex 657c8e5..66d538f 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_dark.png Binary files differindex 5e6ef2b..4e777f8 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_light.png Binary files differindex 342bf11..6062033 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_pressed_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_dark.png Binary files differindex d11ae85..683a883 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_light.png Binary files differindex 68bd1df..19524ff 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_selected_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_on_holo_dark.png Binary files differindex 5b0dbe8..2e1111b 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_on_holo_light.png Binary files differindex 45ae36b..90639ec 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_dark.png Binary files differindex c3a0d48..1907215 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_light.png Binary files differindex ca22358..b51c7ad 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_pressed_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_dark.png Binary files differindex 6c05f47..06d39cc 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_dark.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_light.png Binary files differindex a17fa1e..06a4314 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_light.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_selected_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..500490d --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_light.png Binary files differnew file mode 100644 index 0000000..f6690c6 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_pressed_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_dark.png b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..933d2fe --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_light.png b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_light.png Binary files differnew file mode 100644 index 0000000..c07445a --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_pressed_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..128a8dd --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..da05c23 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..da66a98 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png Binary files differnew file mode 100755 index 0000000..3ac8417 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..fc9d493 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..315ae3b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_holo.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_holo.9.png Binary files differnew file mode 100755 index 0000000..f903bdb --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_holo.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..ee9590c --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png Binary files differnew file mode 100755 index 0000000..cbc9da2 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..1792063 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_light.9.png Binary files differnew file mode 100755 index 0000000..097025b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_off_pressed_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..5b92d7c --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..a244f45 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..9218f91 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png Binary files differnew file mode 100755 index 0000000..81802f8 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..9dea983 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..fc2374b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png Binary files differnew file mode 100755 index 0000000..4c1d89d --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..18bb6bd --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png Binary files differnew file mode 100755 index 0000000..de78a9f --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..2269326 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_light.9.png Binary files differnew file mode 100755 index 0000000..ead4ccf --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_on_pressed_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/cab_divider_holo_dark.png b/core/res/res/drawable-hdpi/cab_divider_holo_dark.png Binary files differnew file mode 100755 index 0000000..e6f61fc --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_divider_holo_dark.png diff --git a/core/res/res/drawable-hdpi/cab_divider_holo_light.png b/core/res/res/drawable-hdpi/cab_divider_holo_light.png Binary files differnew file mode 100755 index 0000000..2f97a29 --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_divider_holo_light.png diff --git a/core/res/res/drawable-hdpi/cab_divider_vertical_dark.png b/core/res/res/drawable-hdpi/cab_divider_vertical_dark.png Binary files differnew file mode 100755 index 0000000..b1f035c --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_divider_vertical_dark.png diff --git a/core/res/res/drawable-hdpi/cab_divider_vertical_light.png b/core/res/res/drawable-hdpi/cab_divider_vertical_light.png Binary files differnew file mode 100755 index 0000000..2183b12 --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_divider_vertical_light.png diff --git a/core/res/res/drawable-hdpi/cab_holo_dark.9.png b/core/res/res/drawable-hdpi/cab_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..662d63c --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/cab_holo_light.9.png b/core/res/res/drawable-hdpi/cab_holo_light.9.png Binary files differnew file mode 100755 index 0000000..e8cbde1 --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/cab_ic_close_focused_holo.png b/core/res/res/drawable-hdpi/cab_ic_close_focused_holo.png Binary files differnew file mode 100755 index 0000000..861e0a1 --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_ic_close_focused_holo.png diff --git a/core/res/res/drawable-hdpi/cab_ic_close_normal_holo.png b/core/res/res/drawable-hdpi/cab_ic_close_normal_holo.png Binary files differnew file mode 100755 index 0000000..036f362 --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_ic_close_normal_holo.png diff --git a/core/res/res/drawable-hdpi/cab_ic_close_pressed_holo.png b/core/res/res/drawable-hdpi/cab_ic_close_pressed_holo.png Binary files differnew file mode 100755 index 0000000..be8c2ff --- /dev/null +++ b/core/res/res/drawable-hdpi/cab_ic_close_pressed_holo.png diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..6de74a7 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_light.png Binary files differnew file mode 100644 index 0000000..a0e201d --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_disabled_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..9bb69c4 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_light.png Binary files differnew file mode 100644 index 0000000..bffc5aa --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_disabled_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/checkbox_focused_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..bbe04b6 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_focused_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_light.png Binary files differnew file mode 100644 index 0000000..62f1efa --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_focused_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/checkbox_focused_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..c4026a8 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_focused_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_light.png Binary files differnew file mode 100644 index 0000000..409aa3e --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_focused_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/checkbox_normal_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..10f1bc4 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_normal_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_light.png Binary files differnew file mode 100644 index 0000000..20a1efa --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_normal_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/checkbox_normal_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..0a10ec8 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_normal_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_light.png Binary files differnew file mode 100644 index 0000000..34b53ee --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_normal_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..7f14620 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_light.png b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_light.png Binary files differnew file mode 100644 index 0000000..cabf936 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_pressed_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_dark.png b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..bcddb31 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_light.png b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_light.png Binary files differnew file mode 100644 index 0000000..84160e5 --- /dev/null +++ b/core/res/res/drawable-hdpi/checkbox_pressed_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png b/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png Binary files differnew file mode 100644 index 0000000..3a84de9 --- /dev/null +++ b/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png diff --git a/core/res/res/drawable-hdpi/dialog_full_holo.9.png b/core/res/res/drawable-hdpi/dialog_full_holo.9.png Binary files differnew file mode 100644 index 0000000..5d2e4e1 --- /dev/null +++ b/core/res/res/drawable-hdpi/dialog_full_holo.9.png diff --git a/core/res/res/drawable-hdpi/dialog_middle_holo.9.png b/core/res/res/drawable-hdpi/dialog_middle_holo.9.png Binary files differnew file mode 100644 index 0000000..dc5e79d --- /dev/null +++ b/core/res/res/drawable-hdpi/dialog_middle_holo.9.png diff --git a/core/res/res/drawable-hdpi/dialog_top_holo.9.png b/core/res/res/drawable-hdpi/dialog_top_holo.9.png Binary files differnew file mode 100644 index 0000000..0275c18 --- /dev/null +++ b/core/res/res/drawable-hdpi/dialog_top_holo.9.png diff --git a/core/res/res/drawable-hdpi/divider_horizontal_holo_dark.9.png b/core/res/res/drawable-hdpi/divider_horizontal_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..e8e1deb --- /dev/null +++ b/core/res/res/drawable-hdpi/divider_horizontal_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/divider_horizontal_holo_light.9.png b/core/res/res/drawable-hdpi/divider_horizontal_holo_light.9.png Binary files differnew file mode 100644 index 0000000..9e6cbbe --- /dev/null +++ b/core/res/res/drawable-hdpi/divider_horizontal_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/divider_vertical_holo_dark.9.png b/core/res/res/drawable-hdpi/divider_vertical_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..deacb73 --- /dev/null +++ b/core/res/res/drawable-hdpi/divider_vertical_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/divider_vertical_holo_light.9.png b/core/res/res/drawable-hdpi/divider_vertical_holo_light.9.png Binary files differnew file mode 100644 index 0000000..cd2c826 --- /dev/null +++ b/core/res/res/drawable-hdpi/divider_vertical_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/ic_dialog_close_normal_holo.png b/core/res/res/drawable-hdpi/ic_dialog_close_normal_holo.png Binary files differnew file mode 100644 index 0000000..5ee5bb8 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_dialog_close_normal_holo.png diff --git a/core/res/res/drawable-hdpi/ic_dialog_close_pressed_holo.png b/core/res/res/drawable-hdpi/ic_dialog_close_pressed_holo.png Binary files differnew file mode 100644 index 0000000..792db06 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_dialog_close_pressed_holo.png diff --git a/core/res/res/drawable-hdpi/ic_dialog_focused_holo.png b/core/res/res/drawable-hdpi/ic_dialog_focused_holo.png Binary files differnew file mode 100644 index 0000000..e208575 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_dialog_focused_holo.png diff --git a/core/res/res/drawable-hdpi/tab_arrow_left_holo_dark.png b/core/res/res/drawable-hdpi/tab_arrow_left_holo_dark.png Binary files differnew file mode 100644 index 0000000..cfd6f78 --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_arrow_left_holo_dark.png diff --git a/core/res/res/drawable-hdpi/tab_arrow_left_holo_light.png b/core/res/res/drawable-hdpi/tab_arrow_left_holo_light.png Binary files differnew file mode 100644 index 0000000..036aa8c --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_arrow_left_holo_light.png diff --git a/core/res/res/drawable-hdpi/tab_arrow_right_holo_dark.png b/core/res/res/drawable-hdpi/tab_arrow_right_holo_dark.png Binary files differnew file mode 100644 index 0000000..b226038 --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_arrow_right_holo_dark.png diff --git a/core/res/res/drawable-hdpi/tab_arrow_right_holo_light.png b/core/res/res/drawable-hdpi/tab_arrow_right_holo_light.png Binary files differnew file mode 100644 index 0000000..0e5fbe6 --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_arrow_right_holo_light.png diff --git a/core/res/res/drawable-hdpi/tab_divider_holo_dark.png b/core/res/res/drawable-hdpi/tab_divider_holo_dark.png Binary files differnew file mode 100644 index 0000000..112cb04 --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_divider_holo_dark.png diff --git a/core/res/res/drawable-hdpi/tab_divider_holo_light.png b/core/res/res/drawable-hdpi/tab_divider_holo_light.png Binary files differnew file mode 100644 index 0000000..1bf4d38 --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_divider_holo_light.png diff --git a/core/res/res/drawable-hdpi/tab_selector_holo_dark.9.png b/core/res/res/drawable-hdpi/tab_selector_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..f01b9bc --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_selector_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/tab_strip_holo.9.png b/core/res/res/drawable-hdpi/tab_strip_holo.9.png Binary files differnew file mode 100644 index 0000000..d937f6b --- /dev/null +++ b/core/res/res/drawable-hdpi/tab_strip_holo.9.png diff --git a/core/res/res/drawable-hdpi/textfield_default_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_default_holo_dark.9.png Binary files differindex c67d04d..7ec2192 100644 --- a/core/res/res/drawable-hdpi/textfield_default_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/textfield_default_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/textfield_default_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_default_holo_light.9.png Binary files differindex 1292ebf..c03e4f6 100644 --- a/core/res/res/drawable-hdpi/textfield_default_holo_light.9.png +++ b/core/res/res/drawable-hdpi/textfield_default_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/textfield_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_disabled_holo_dark.9.png Binary files differindex a39982a..6642717 100644 --- a/core/res/res/drawable-hdpi/textfield_disabled_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/textfield_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/textfield_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_disabled_holo_light.9.png Binary files differindex 1f224b9..9572752 100644 --- a/core/res/res/drawable-hdpi/textfield_disabled_holo_light.9.png +++ b/core/res/res/drawable-hdpi/textfield_disabled_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_dark.9.png Binary files differindex 66f18cc..0ad248c 100644 --- a/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_light.9.png Binary files differindex 2664384..b7a07c4 100644 --- a/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_light.9.png +++ b/core/res/res/drawable-hdpi/textfield_disabled_selected_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/textfield_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_pressed_holo_dark.9.png Binary files differindex bbf53c5..a271ac9 100644 --- a/core/res/res/drawable-hdpi/textfield_pressed_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/textfield_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/textfield_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_pressed_holo_light.9.png Binary files differindex ce49b8d..521722d 100644 --- a/core/res/res/drawable-hdpi/textfield_pressed_holo_light.9.png +++ b/core/res/res/drawable-hdpi/textfield_pressed_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/textfield_search_default_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_search_default_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..5b62564 --- /dev/null +++ b/core/res/res/drawable-hdpi/textfield_search_default_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/textfield_search_default_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_search_default_holo_light.9.png Binary files differnew file mode 100644 index 0000000..881edeb --- /dev/null +++ b/core/res/res/drawable-hdpi/textfield_search_default_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/textfield_search_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_search_selected_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..cb3f35b --- /dev/null +++ b/core/res/res/drawable-hdpi/textfield_search_selected_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/textfield_search_selected_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_search_selected_holo_light.9.png Binary files differnew file mode 100644 index 0000000..742b137 --- /dev/null +++ b/core/res/res/drawable-hdpi/textfield_search_selected_holo_light.9.png diff --git a/core/res/res/drawable-hdpi/textfield_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/textfield_selected_holo_dark.9.png Binary files differindex bbf53c5..a271ac9 100644 --- a/core/res/res/drawable-hdpi/textfield_selected_holo_dark.9.png +++ b/core/res/res/drawable-hdpi/textfield_selected_holo_dark.9.png diff --git a/core/res/res/drawable-hdpi/textfield_selected_holo_light.9.png b/core/res/res/drawable-hdpi/textfield_selected_holo_light.9.png Binary files differindex ce49b8d..521722d 100644 --- a/core/res/res/drawable-hdpi/textfield_selected_holo_light.9.png +++ b/core/res/res/drawable-hdpi/textfield_selected_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_dark.png Binary files differindex a603fb1..3fac4aa 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_light.png Binary files differindex 69e9ff9..3da9a46 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_dark.png Binary files differindex a603fb1..3fac4aa 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_disable_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_light.png Binary files differindex 69e9ff9..3da9a46 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_disable_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_off_disable_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_holo_dark.png Binary files differindex 5e44c29..b03f356 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_holo_light.png Binary files differindex 5b2ec92..9dbbd49 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_dark.png Binary files differindex 611bb1d..0dcb9de 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_light.png Binary files differindex 5a0ea44..b25fdc4 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_off_pressed_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_dark.png Binary files differindex aa28df2..89ea3a8 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_selected_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_light.png Binary files differindex ade1136..3fa45d9 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_selected_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_off_selected_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png Binary files differindex f19972a..48cc017 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_light.png Binary files differindex 13ef46e..c9ebbca 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_dark.png Binary files differindex f19972a..48cc017 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_disable_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_light.png Binary files differindex 13ef46e..c9ebbca 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_disable_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_on_disable_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_holo_dark.png Binary files differindex 130d562..ca4d509 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_holo_light.png Binary files differindex 6b7808b..158f3a1 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_dark.png Binary files differindex df753f5..0722cda 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_light.png Binary files differindex 6a4dd2c..4de166e 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_on_pressed_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_dark.png Binary files differindex 7586881..5b93f8b 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_selected_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_light.png Binary files differindex 24701ce..74ab250 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_selected_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_check_on_selected_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..9bc1ee8 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png Binary files differnew file mode 100644 index 0000000..cc643ea --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..0586d52 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png Binary files differnew file mode 100644 index 0000000..dd6c1a0 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_disabled_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..e8f07cb --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png Binary files differnew file mode 100644 index 0000000..0685f1e --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..6b33fa4 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_normal_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png Binary files differnew file mode 100644 index 0000000..eb728ed --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_normal_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..4a15d9d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png Binary files differnew file mode 100644 index 0000000..6718ff7 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_default_pressed_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..f21142e --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_light.png Binary files differnew file mode 100644 index 0000000..a1031fc --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_disabled_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..61243c5 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_light.png Binary files differnew file mode 100644 index 0000000..faa55e0 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_disabled_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..0c645da --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_light.png Binary files differnew file mode 100644 index 0000000..5efc321 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_focused_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..96bcdc5 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_light.png Binary files differnew file mode 100644 index 0000000..5efc321 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_focused_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..96413ef --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_light.png Binary files differnew file mode 100644 index 0000000..1cb5432 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_normal_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..2e8404a --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_light.png Binary files differnew file mode 100644 index 0000000..b3e14b1 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_normal_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_off_holo_dark.png Binary files differindex 16c1c6b..a3cef04 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_off_holo_light.png Binary files differindex e8287f3..e8def55 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_dark.png Binary files differindex b25217b..1a9310b 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_light.png Binary files differindex b63b9b0..bc28b5b 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_pressed_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_dark.png Binary files differindex bef7572..2b0ddd1 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_light.png Binary files differindex af754e1..745cf19 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_selected_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_on_holo_dark.png Binary files differindex 4ed7471..9954500 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_on_holo_light.png Binary files differindex 62aaa41..fa67a43 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_dark.png Binary files differindex 7cf91c6..c15c310 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_light.png Binary files differindex 0d93507..aa07c5a 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_pressed_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_dark.png Binary files differindex 56f6f5b..e96a74f 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_dark.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_light.png Binary files differindex 48dd8e9..c51c96c 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_light.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_selected_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..5f74c70 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_light.png Binary files differnew file mode 100644 index 0000000..408e50e --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_pressed_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_dark.png b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..ff60bc2 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_light.png b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_light.png Binary files differnew file mode 100644 index 0000000..2125c24 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_pressed_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..7d1e16d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..92e86cd --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..1cf473b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png Binary files differnew file mode 100755 index 0000000..d6f2125 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..31f7f8c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..82425d5 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_holo.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_holo.9.png Binary files differnew file mode 100755 index 0000000..0ca659e --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_holo.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..16f19fc --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png Binary files differnew file mode 100755 index 0000000..e2c7702 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..d61470c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_light.9.png Binary files differnew file mode 100755 index 0000000..4019fee --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_off_pressed_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..ba354e3 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..9391b2e --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..601ff2c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png Binary files differnew file mode 100755 index 0000000..90c259a --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..857c757 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png Binary files differnew file mode 100755 index 0000000..ef16a48 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png Binary files differnew file mode 100755 index 0000000..66cbe48 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..d47ec8f --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png Binary files differnew file mode 100755 index 0000000..2951caf --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..141b4dd --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_light.9.png Binary files differnew file mode 100755 index 0000000..913aed5 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_on_pressed_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/cab_divider_holo_dark.png b/core/res/res/drawable-mdpi/cab_divider_holo_dark.png Binary files differnew file mode 100755 index 0000000..57cc8a4 --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_divider_holo_dark.png diff --git a/core/res/res/drawable-mdpi/cab_divider_holo_light.png b/core/res/res/drawable-mdpi/cab_divider_holo_light.png Binary files differnew file mode 100755 index 0000000..ec85701 --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_divider_holo_light.png diff --git a/core/res/res/drawable-mdpi/cab_divider_vertical_dark.png b/core/res/res/drawable-mdpi/cab_divider_vertical_dark.png Binary files differnew file mode 100755 index 0000000..f7ed6df --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_divider_vertical_dark.png diff --git a/core/res/res/drawable-mdpi/cab_divider_vertical_light.png b/core/res/res/drawable-mdpi/cab_divider_vertical_light.png Binary files differnew file mode 100755 index 0000000..73ac0d9 --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_divider_vertical_light.png diff --git a/core/res/res/drawable-mdpi/cab_holo_dark.9.png b/core/res/res/drawable-mdpi/cab_holo_dark.9.png Binary files differnew file mode 100755 index 0000000..6c85300 --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/cab_holo_light.9.png b/core/res/res/drawable-mdpi/cab_holo_light.9.png Binary files differnew file mode 100755 index 0000000..c82352a --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/cab_ic_close_focused_holo.png b/core/res/res/drawable-mdpi/cab_ic_close_focused_holo.png Binary files differnew file mode 100755 index 0000000..df170c4 --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_ic_close_focused_holo.png diff --git a/core/res/res/drawable-mdpi/cab_ic_close_normal_holo.png b/core/res/res/drawable-mdpi/cab_ic_close_normal_holo.png Binary files differnew file mode 100755 index 0000000..9482ce7 --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_ic_close_normal_holo.png diff --git a/core/res/res/drawable-mdpi/cab_ic_close_pressed_holo.png b/core/res/res/drawable-mdpi/cab_ic_close_pressed_holo.png Binary files differnew file mode 100755 index 0000000..d115d20 --- /dev/null +++ b/core/res/res/drawable-mdpi/cab_ic_close_pressed_holo.png diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..e1094be --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_light.png Binary files differnew file mode 100644 index 0000000..c17377c --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_disabled_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..f2c5290 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_light.png Binary files differnew file mode 100644 index 0000000..06bd903 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_disabled_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/checkbox_focused_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..be624c2 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_focused_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_light.png Binary files differnew file mode 100644 index 0000000..2493ce2 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_focused_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/checkbox_focused_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..7cdc1df --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_focused_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_light.png Binary files differnew file mode 100644 index 0000000..f977e72 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_focused_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/checkbox_normal_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..f824f76 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_normal_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_light.png Binary files differnew file mode 100644 index 0000000..a76f68c --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_normal_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/checkbox_normal_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..e4fd418 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_normal_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_light.png Binary files differnew file mode 100644 index 0000000..d572ef5 --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_normal_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_dark.png Binary files differnew file mode 100644 index 0000000..686707e --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_light.png b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_light.png Binary files differnew file mode 100644 index 0000000..17dd1da --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_pressed_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_dark.png b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_dark.png Binary files differnew file mode 100644 index 0000000..8cf2b1b --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_light.png b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_light.png Binary files differnew file mode 100644 index 0000000..c2df6da --- /dev/null +++ b/core/res/res/drawable-mdpi/checkbox_pressed_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png b/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png Binary files differnew file mode 100644 index 0000000..cb3d0f2 --- /dev/null +++ b/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png diff --git a/core/res/res/drawable-mdpi/dialog_full_holo.9.png b/core/res/res/drawable-mdpi/dialog_full_holo.9.png Binary files differnew file mode 100644 index 0000000..0ec9421 --- /dev/null +++ b/core/res/res/drawable-mdpi/dialog_full_holo.9.png diff --git a/core/res/res/drawable-mdpi/dialog_middle_holo.9.png b/core/res/res/drawable-mdpi/dialog_middle_holo.9.png Binary files differnew file mode 100644 index 0000000..36da5ca --- /dev/null +++ b/core/res/res/drawable-mdpi/dialog_middle_holo.9.png diff --git a/core/res/res/drawable-mdpi/dialog_top_holo.9.png b/core/res/res/drawable-mdpi/dialog_top_holo.9.png Binary files differnew file mode 100644 index 0000000..1ed519b --- /dev/null +++ b/core/res/res/drawable-mdpi/dialog_top_holo.9.png diff --git a/core/res/res/drawable-mdpi/divider_horizontal_holo_dark.9.png b/core/res/res/drawable-mdpi/divider_horizontal_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..d6548c6 --- /dev/null +++ b/core/res/res/drawable-mdpi/divider_horizontal_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/divider_horizontal_holo_light.9.png b/core/res/res/drawable-mdpi/divider_horizontal_holo_light.9.png Binary files differnew file mode 100644 index 0000000..9a42dd2 --- /dev/null +++ b/core/res/res/drawable-mdpi/divider_horizontal_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/divider_vertical_holo_dark.9.png b/core/res/res/drawable-mdpi/divider_vertical_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..4c6968c --- /dev/null +++ b/core/res/res/drawable-mdpi/divider_vertical_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/divider_vertical_holo_light.9.png b/core/res/res/drawable-mdpi/divider_vertical_holo_light.9.png Binary files differnew file mode 100644 index 0000000..7ddf1b6 --- /dev/null +++ b/core/res/res/drawable-mdpi/divider_vertical_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/ic_dialog_close_normal_holo.png b/core/res/res/drawable-mdpi/ic_dialog_close_normal_holo.png Binary files differnew file mode 100644 index 0000000..7f2a029 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_dialog_close_normal_holo.png diff --git a/core/res/res/drawable-mdpi/ic_dialog_close_pressed_holo.png b/core/res/res/drawable-mdpi/ic_dialog_close_pressed_holo.png Binary files differnew file mode 100644 index 0000000..daa8e18 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_dialog_close_pressed_holo.png diff --git a/core/res/res/drawable-mdpi/ic_dialog_focused_holo.png b/core/res/res/drawable-mdpi/ic_dialog_focused_holo.png Binary files differnew file mode 100644 index 0000000..179f2de --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_dialog_focused_holo.png diff --git a/core/res/res/drawable-mdpi/tab_arrow_left_holo_dark.png b/core/res/res/drawable-mdpi/tab_arrow_left_holo_dark.png Binary files differnew file mode 100644 index 0000000..4f8bafe --- /dev/null +++ b/core/res/res/drawable-mdpi/tab_arrow_left_holo_dark.png diff --git a/core/res/res/drawable-mdpi/tab_arrow_left_holo_light.png b/core/res/res/drawable-mdpi/tab_arrow_left_holo_light.png Binary files differnew file mode 100644 index 0000000..8e225fc --- /dev/null +++ b/core/res/res/drawable-mdpi/tab_arrow_left_holo_light.png diff --git a/core/res/res/drawable-mdpi/tab_arrow_right_holo_dark.png b/core/res/res/drawable-mdpi/tab_arrow_right_holo_dark.png Binary files differnew file mode 100644 index 0000000..0a8006d --- /dev/null +++ b/core/res/res/drawable-mdpi/tab_arrow_right_holo_dark.png diff --git a/core/res/res/drawable-mdpi/tab_arrow_right_holo_light.png b/core/res/res/drawable-mdpi/tab_arrow_right_holo_light.png Binary files differnew file mode 100644 index 0000000..85aae47 --- /dev/null +++ b/core/res/res/drawable-mdpi/tab_arrow_right_holo_light.png diff --git a/core/res/res/drawable-mdpi/tab_divider_holo_dark.png b/core/res/res/drawable-mdpi/tab_divider_holo_dark.png Binary files differnew file mode 100644 index 0000000..89d7b8b --- /dev/null +++ b/core/res/res/drawable-mdpi/tab_divider_holo_dark.png diff --git a/core/res/res/drawable-mdpi/tab_divider_holo_light.png b/core/res/res/drawable-mdpi/tab_divider_holo_light.png Binary files differnew file mode 100644 index 0000000..878b2b4 --- /dev/null +++ b/core/res/res/drawable-mdpi/tab_divider_holo_light.png diff --git a/core/res/res/drawable-mdpi/tab_selector_holo_dark.9.png b/core/res/res/drawable-mdpi/tab_selector_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..30d30df --- /dev/null +++ b/core/res/res/drawable-mdpi/tab_selector_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_default_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_default_holo_dark.9.png Binary files differindex a9a2cc6..3a5f36d 100644 --- a/core/res/res/drawable-mdpi/textfield_default_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/textfield_default_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_default_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_default_holo_light.9.png Binary files differindex 36003cf..b8cc76f 100644 --- a/core/res/res/drawable-mdpi/textfield_default_holo_light.9.png +++ b/core/res/res/drawable-mdpi/textfield_default_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/textfield_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_disabled_holo_dark.9.png Binary files differindex cec82dd..a1f0c71 100644 --- a/core/res/res/drawable-mdpi/textfield_disabled_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/textfield_disabled_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_disabled_holo_light.9.png Binary files differindex 8a9b11f..71e3103 100644 --- a/core/res/res/drawable-mdpi/textfield_disabled_holo_light.9.png +++ b/core/res/res/drawable-mdpi/textfield_disabled_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_dark.9.png Binary files differindex 545924c..ac6d406 100644 --- a/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_light.9.png Binary files differindex 53ee26c..bb6e953 100644 --- a/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_light.9.png +++ b/core/res/res/drawable-mdpi/textfield_disabled_selected_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/textfield_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_pressed_holo_dark.9.png Binary files differindex 0e7c24d..7667d95 100644 --- a/core/res/res/drawable-mdpi/textfield_pressed_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/textfield_pressed_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_pressed_holo_light.9.png Binary files differindex fee09a6..269affd 100644 --- a/core/res/res/drawable-mdpi/textfield_pressed_holo_light.9.png +++ b/core/res/res/drawable-mdpi/textfield_pressed_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/textfield_search_default_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_search_default_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..3549948 --- /dev/null +++ b/core/res/res/drawable-mdpi/textfield_search_default_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_search_default_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_search_default_holo_light.9.png Binary files differnew file mode 100644 index 0000000..6db03cb --- /dev/null +++ b/core/res/res/drawable-mdpi/textfield_search_default_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/textfield_search_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_search_selected_holo_dark.9.png Binary files differnew file mode 100644 index 0000000..670439a --- /dev/null +++ b/core/res/res/drawable-mdpi/textfield_search_selected_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_search_selected_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_search_selected_holo_light.9.png Binary files differnew file mode 100644 index 0000000..90e26e2 --- /dev/null +++ b/core/res/res/drawable-mdpi/textfield_search_selected_holo_light.9.png diff --git a/core/res/res/drawable-mdpi/textfield_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/textfield_selected_holo_dark.9.png Binary files differindex 0e7c24d..7667d95 100644 --- a/core/res/res/drawable-mdpi/textfield_selected_holo_dark.9.png +++ b/core/res/res/drawable-mdpi/textfield_selected_holo_dark.9.png diff --git a/core/res/res/drawable-mdpi/textfield_selected_holo_light.9.png b/core/res/res/drawable-mdpi/textfield_selected_holo_light.9.png Binary files differindex fee09a6..269affd 100644 --- a/core/res/res/drawable-mdpi/textfield_selected_holo_light.9.png +++ b/core/res/res/drawable-mdpi/textfield_selected_holo_light.9.png diff --git a/core/res/res/drawable/btn_default_holo_dark.xml b/core/res/res/drawable/btn_default_holo_dark.xml new file mode 100644 index 0000000..c250070 --- /dev/null +++ b/core/res/res/drawable/btn_default_holo_dark.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_window_focused="false" android:state_enabled="true" + android:drawable="@drawable/btn_default_normal_holo_dark" /> + <item android:state_window_focused="false" android:state_enabled="false" + android:drawable="@drawable/btn_default_disabled_holo_dark" /> + <item android:state_pressed="true" + android:drawable="@drawable/btn_default_pressed_holo_dark" /> + <item android:state_focused="true" android:state_enabled="true" + android:drawable="@drawable/btn_default_focused_holo_dark" /> + <item android:state_enabled="true" + android:drawable="@drawable/btn_default_normal_holo_dark" /> + <item android:state_focused="true" + android:drawable="@drawable/btn_default_disabled_focused_holo_dark" /> + <item + android:drawable="@drawable/btn_default_disabled_holo_dark" /> +</selector> diff --git a/core/res/res/drawable/btn_toggle_holo_dark.xml b/core/res/res/drawable/btn_toggle_holo_dark.xml new file mode 100644 index 0000000..658ca9e --- /dev/null +++ b/core/res/res/drawable/btn_toggle_holo_dark.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" + android:state_window_focused="false" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_on_normal_holo_dark" /> + <item android:state_checked="true" + android:state_window_focused="false" android:state_enabled="false" + android:drawable="@drawable/btn_toggle_on_disabled_holo_dark" /> + <item android:state_checked="true" android:state_pressed="true" + android:drawable="@drawable/btn_toggle_on_pressed_holo_dark" /> + <item android:state_checked="true" + android:state_focused="true" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_on_focused_holo_dark" /> + <item android:state_checked="true" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_on_normal_holo_dark" /> + <item android:state_checked="true" android:state_focused="true" + android:drawable="@drawable/btn_toggle_on_disabled_focused_holo_dark" /> + <item android:state_checked="true" + android:drawable="@drawable/btn_toggle_on_disabled_holo_dark" /> + + <item android:state_window_focused="false" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_off_normal_holo_dark" /> + <item android:state_window_focused="false" android:state_enabled="false" + android:drawable="@drawable/btn_toggle_off_disabled_holo_dark" /> + <item android:state_pressed="true" + android:drawable="@drawable/btn_toggle_off_pressed_holo_dark" /> + <item android:state_focused="true" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_off_focused_holo_dark" /> + <item android:state_enabled="true" + android:drawable="@drawable/btn_toggle_off_normal_holo_dark" /> + <item android:state_focused="true" + android:drawable="@drawable/btn_toggle_off_disabled_focused_holo_dark" /> + <item + android:drawable="@drawable/btn_toggle_off_disabled_holo_dark" /> +</selector> diff --git a/core/res/res/drawable/btn_toggle_holo_light.xml b/core/res/res/drawable/btn_toggle_holo_light.xml new file mode 100644 index 0000000..d3f1012 --- /dev/null +++ b/core/res/res/drawable/btn_toggle_holo_light.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" + android:state_window_focused="false" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_on_normal_holo_light" /> + <item android:state_checked="true" + android:state_window_focused="false" android:state_enabled="false" + android:drawable="@drawable/btn_toggle_on_disabled_holo_light" /> + <item android:state_checked="true" android:state_pressed="true" + android:drawable="@drawable/btn_toggle_on_pressed_holo_light" /> + <item android:state_checked="true" + android:state_focused="true" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_on_focused_holo_light" /> + <item android:state_checked="true" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_on_normal_holo_light" /> + <item android:state_checked="true" android:state_focused="true" + android:drawable="@drawable/btn_toggle_on_disabled_focused_holo_light" /> + <item android:state_checked="true" + android:drawable="@drawable/btn_toggle_on_disabled_holo_light" /> + + <item android:state_window_focused="false" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_off_normal_holo_light" /> + <item android:state_window_focused="false" android:state_enabled="false" + android:drawable="@drawable/btn_toggle_off_disabled_holo_light" /> + <item android:state_pressed="true" + android:drawable="@drawable/btn_toggle_off_pressed_holo_light" /> + <item android:state_focused="true" android:state_enabled="true" + android:drawable="@drawable/btn_toggle_off_focused_holo_light" /> + <item android:state_enabled="true" + android:drawable="@drawable/btn_toggle_off_normal_holo_light" /> + <item android:state_focused="true" + android:drawable="@drawable/btn_toggle_off_disabled_focused_holo_light" /> + <item + android:drawable="@drawable/btn_toggle_off_disabled_holo_light" /> +</selector> diff --git a/core/res/res/drawable/cab_ic_close_holo.xml b/core/res/res/drawable/cab_ic_close_holo.xml new file mode 100644 index 0000000..1baf7cc --- /dev/null +++ b/core/res/res/drawable/cab_ic_close_holo.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" + android:drawable="@drawable/cab_ic_close_pressed_holo" /> + <item android:state_focused="true" + android:drawable="@drawable/cab_ic_close_focused_holo" /> + <item android:drawable="@drawable/cab_ic_close_normal_holo" /> +</selector> diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml index c665f7a..94dcb6a 100644 --- a/core/res/res/layout/select_dialog.xml +++ b/core/res/res/layout/select_dialog.xml @@ -30,5 +30,5 @@ android:layout_height="match_parent" android:layout_marginTop="5px" android:cacheColorHint="@null" - android:divider="@android:drawable/divider_horizontal_bright" + android:divider="?android:attr/listDividerAlertDialog" android:scrollbars="vertical" /> diff --git a/core/res/res/layout/select_dialog_item.xml b/core/res/res/layout/select_dialog_item.xml index 30fe02e..96fdcc6 100644 --- a/core/res/res/layout/select_dialog_item.xml +++ b/core/res/res/layout/select_dialog_item.xml @@ -29,7 +29,7 @@ android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="@android:color/primary_text_light_disable_only" + android:textColor="?android:attr/textColorAlertDialogListItem" android:gravity="center_vertical" android:paddingLeft="14dip" android:paddingRight="15dip" diff --git a/core/res/res/layout/select_dialog_multichoice.xml b/core/res/res/layout/select_dialog_multichoice.xml index b646a4c..a9be014 100644 --- a/core/res/res/layout/select_dialog_multichoice.xml +++ b/core/res/res/layout/select_dialog_multichoice.xml @@ -20,7 +20,7 @@ android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="@android:color/primary_text_light_disable_only" + android:textColor="?android:attr/textColorAlertDialogListItem" android:gravity="center_vertical" android:paddingLeft="12dip" android:paddingRight="7dip" diff --git a/core/res/res/layout/select_dialog_singlechoice.xml b/core/res/res/layout/select_dialog_singlechoice.xml index c3c2073..1b9c973 100644 --- a/core/res/res/layout/select_dialog_singlechoice.xml +++ b/core/res/res/layout/select_dialog_singlechoice.xml @@ -20,7 +20,7 @@ android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="@android:color/primary_text_light_disable_only" + android:textColor="?android:attr/textColorAlertDialogListItem" android:gravity="center_vertical" android:paddingLeft="12dip" android:paddingRight="7dip" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 19b76ab..317b3f3 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -101,6 +101,9 @@ <attr name="textColorHighlightInverse" format="reference|color" /> <!-- Color of link text (URLs), when used in a light theme. @hide --> <attr name="textColorLinkInverse" format="reference|color" /> + + <!-- Color of list item text in alert dialogs. --> + <attr name="textColorAlertDialogListItem" format="reference|color" /> <!-- Search widget more corpus result item background. --> <attr name="searchWidgetCorpusItemBackground" format="reference|color" /> @@ -197,6 +200,8 @@ <!-- The list item height for search results. @hide --> <attr name="searchResultListItemHeight" format="dimension" /> <attr name="listDivider" format="reference" /> + <!-- The list divider used in alert dialogs. --> + <attr name="listDividerAlertDialog" format="reference" /> <!-- TextView style for list separators. --> <attr name="listSeparatorTextViewStyle" format="reference" /> <!-- The preferred left padding for an expandable list item (for child-specific layouts, diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index e9a4ca3..623368d 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -18,8 +18,8 @@ */ --> <resources> - <drawable name="screen_background_light">#ffefefef</drawable> - <drawable name="screen_background_dark">#ff101010</drawable> + <drawable name="screen_background_light">#fff3f3f3</drawable> + <drawable name="screen_background_dark">#ff000000</drawable> <drawable name="status_bar_closed_default_background">#ff000000</drawable> <drawable name="status_bar_opened_default_background">#ff000000</drawable> <drawable name="search_bar_default_color">#ff000000</drawable> @@ -36,8 +36,8 @@ <color name="white">#ffffffff</color> <color name="black">#ff000000</color> <color name="transparent">#00000000</color> - <color name="background_dark">#ff101010</color> - <color name="background_light">#ffefefef</color> + <color name="background_dark">#ff000000</color> + <color name="background_light">#fff3f3f3</color> <color name="bright_foreground_dark">@android:color/background_light</color> <color name="bright_foreground_light">@android:color/background_dark</color> <color name="bright_foreground_dark_disabled">#80ffffff</color> @@ -103,5 +103,32 @@ <color name="keyguard_text_color_soundon">#e69310</color> <color name="keyguard_text_color_decline">#fe0a5a</color> + <!-- For holo theme --> + <drawable name="screen_background_holo_light">#fff3f3f3</drawable> + <drawable name="screen_background_holo_dark">#ff000000</drawable> + <color name="background_holo_dark">#ff000000</color> + <color name="background_holo_light">#fff3f3f3</color> + <color name="bright_foreground_holo_dark">@android:color/background_holo_light</color> + <color name="bright_foreground_holo_light">@android:color/background_holo_dark</color> + <color name="bright_foreground_disabled_holo_dark">#80ffffff</color> + <color name="bright_foreground_disabled_holo_light">#80000000</color> + <color name="bright_foreground_inverse_holo_dark">@android:color/bright_foreground_holo_light</color> + <color name="bright_foreground_inverse_holo_light">@android:color/bright_foreground_holo_dark</color> + <color name="dim_foreground_holo_dark">#bebebe</color> + <color name="dim_foreground_disabled_holo_dark">#80bebebe</color> + <color name="dim_foreground_inverse_holo_dark">#323232</color> + <color name="dim_foreground_inverse_disabled_holo_dark">#80323232</color> + <color name="hint_foreground_holo_dark">#808080</color> + <color name="dim_foreground_holo_light">#323232</color> + <color name="dim_foreground_disabled_holo_light">#80323232</color> + <color name="dim_foreground_inverse_holo_light">#bebebe</color> + <color name="dim_foreground_inverse_disabled_holo_light">#80bebebe</color> + <color name="hint_foreground_holo_light">#808080</color> + <color name="highlight_background_holo">#cc475925</color> + <color name="highlight_background_inverse_holo">#ccd2e461</color> + <color name="highlighted_text_holo_dark">#cc475925</color> + <color name="highlighted_text_holo_light">#ccd2e461</color> + <color name="link_text_holo_dark">#5c5cff</color> + <color name="link_text_holo_light">#0000ee</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b4e1fcb..f704874 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -37,7 +37,6 @@ <item><xliff:g id="id">speakerphone</xliff:g></item> <item><xliff:g id="id">mute</xliff:g></item> <item><xliff:g id="id">volume</xliff:g></item> - <item><xliff:g id="id">tty</xliff:g></item> <item><xliff:g id="id">wifi</xliff:g></item> <item><xliff:g id="id">cdma_eri</xliff:g></item> <item><xliff:g id="id">data_connection</xliff:g></item> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index cdeb899..adcbb10 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1358,6 +1358,8 @@ <public type="attr" name="textAppearanceSmallPopupMenu" /> <public type="attr" name="breadCrumbTitle" /> <public type="attr" name="breadCrumbShortTitle" /> + <public type="attr" name="listDividerAlertDialog" /> + <public type="attr" name="textColorAlertDialogListItem" /> <public type="anim" name="animator_fade_in" /> <public type="anim" name="animator_fade_out" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 153dd29..7a9b59a 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1157,6 +1157,30 @@ connections with paired devices.</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_nfcAdmin">NFC administration</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_nfcAdmin">Allows an application to configure + the local NFC phone.</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_nfcRaw">NFC full access to remote device</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_nfcRaw">Allows an application to access + remote NFC devices.</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_nfcNotify">NFC notification from remote device</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_nfcNotify">Allows an application to be notified + of operations related to remote NFC devices.</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_nfcLlcp">NFC notification from remote LLCP device</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_nfcLlcp">Allows an application to be notified + of LLCP operations related to remote NFC devices.</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_disableKeyguard">disable keylock</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_disableKeyguard">Allows an application to disable @@ -1611,7 +1635,7 @@ <!-- Do not translate. WebView User Agent string --> <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>) - AppleWebKit/534.8 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.8</string> + AppleWebKit/534.9 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.9</string> <!-- Do not translate. WebView User Agent targeted content --> <string name="web_user_agent_target_content" translatable="false">"Mobile "</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index ebaaada..6e9530c 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -984,4 +984,706 @@ <item name="android:textColor">@android:color/secondary_text_light</item> </style> + <!-- Begin Holo theme styles --> + + <!-- Text Styles --> + <style name="TextAppearance.Holo" parent="TextAppearance"> + </style> + + <style name="TextAppearance.Holo.Inverse" parent="TextAppearance.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.Large" parent="TextAppearance.Large"> + </style> + + <style name="TextAppearance.Holo.Medium" parent="TextAppearance.Medium"> + </style> + + <style name="TextAppearance.Holo.Small" parent="TextAppearance.Small"> + </style> + + <style name="TextAppearance.Holo.Large.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.Medium.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.Small.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.SearchResult"> + </style> + + <style name="TextAppearance.Holo.SearchResult.Title"> + </style> + + <style name="TextAppearance.Holo.SearchResult.Subtitle"> + </style> + + <style name="TextAppearance.Holo.Widget" parent="TextAppearance.Widget"> + </style> + + <style name="TextAppearance.Holo.Widget.Button" parent="TextAppearance.Holo.Small.Inverse"> + <item name="android:textColor">@android:color/primary_text_light_nodisable</item> + </style> + + <style name="TextAppearance.Holo.Widget.IconMenu.Item" parent="TextAppearance.Holo.Small"> + <item name="android:textColor">?textColorPrimary</item> + </style> + + <style name="TextAppearance.Holo.Widget.TabWidget"> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">normal</item> + <item name="android:textColor">@android:color/tab_indicator_text</item> + </style> + + <style name="TextAppearance.Holo.Widget.TextView"> + <item name="android:textColor">?textColorPrimaryDisableOnly</item> + <item name="android:textColorHint">?textColorHint</item> + </style> + + <style name="TextAppearance.Holo.Widget.TextView.PopupMenu"> + <item name="android:textSize">18sp</item> + <item name="android:textColor">?textColorPrimaryDisableOnly</item> + <item name="android:textColorHint">?textColorHint</item> + </style> + + <style name="TextAppearance.Holo.Widget.DropDownHint"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textSize">14sp</item> + </style> + + <style name="TextAppearance.Holo.Widget.DropDownItem"> + <item name="android:textColor">@android:color/primary_text_light_disable_only</item> + </style> + + <style name="TextAppearance.Holo.Widget.TextView.SpinnerItem"> + <item name="android:textColor">@android:color/primary_text_light_disable_only</item> + </style> + + <style name="TextAppearance.Holo.Widget.EditText"> + <item name="android:textColor">@color/widget_edittext_holo_dark</item> + <item name="android:textColorHint">@android:color/hint_foreground_holo_light</item> + </style> + + <style name="TextAppearance.Holo.Widget.PopupMenu" parent="TextAppearance.Widget.PopupMenu"> + </style> + + <style name="TextAppearance.Holo.Widget.PopupMenu.Large" parent="TextAppearance.Widget.PopupMenu.Large"> + </style> + + <style name="TextAppearance.Holo.Widget.PopupMenu.Small" parent="TextAppearance.Widget.PopupMenu.Small"> + </style> + + <style name="TextAppearance.Holo.Widget.ActionBar.Title" + parent="TextAppearance.Holo.Medium"> + </style> + + <style name="TextAppearance.Holo.Widget.ActionBar.Subtitle" + parent="TextAppearance.Holo.Small"> + </style> + + <style name="TextAppearance.Holo.Widget.ActionMode"> + </style> + + <style name="TextAppearance.Holo.Widget.ActionMode.Title" parent="TextAppearance.Widget.ActionMode.Title"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="TextAppearance.Holo.Widget.ActionMode.Subtitle" parent="TextAppearance.Widget.ActionMode.Subtitle"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + </style> + + <style name="TextAppearance.Holo.WindowTitle"> + <item name="android:textColor">#fff</item> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">bold</item> + </style> + + <style name="TextAppearance.Holo.DialogWindowTitle"> + <item name="android:textSize">18sp</item> + </style> + + <!-- Light text styles --> + <style name="TextAppearance.Holo.Light" parent="TextAppearance.Holo"> + </style> + + <style name="TextAppearance.Holo.Light.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.Light.Large"> + </style> + + <style name="TextAppearance.Holo.Light.Medium"> + </style> + + <style name="TextAppearance.Holo.Light.Small"> + </style> + + <style name="TextAppearance.Holo.Light.Large.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.Light.Medium.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.Light.Small.Inverse"> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> + </style> + + <style name="TextAppearance.Holo.Light.SearchResult" parent="TextAppearance.Holo.SearchResult"> + </style> + + <style name="TextAppearance.Holo.Light.SearchResult.Title"> + </style> + + <style name="TextAppearance.Holo.Light.SearchResult.Subtitle"> + </style> + + <style name="TextAppearance.Holo.Light.Widget" parent="TextAppearance.Widget"> + </style> + + <style name="TextAppearance.Holo.Light.Widget.Button"> + </style> + + <style name="TextAppearance.Holo.Light.Widget.EditText"> + <item name="android:textColor">@color/widget_edittext_holo_light</item> + <item name="android:textColorHint">@android:color/hint_foreground_holo_dark</item> + </style> + + <style name="TextAppearance.Holo.Light.Widget.PopupMenu" parent="TextAppearance.Holo.Widget.PopupMenu"> + </style> + + <style name="TextAppearance.Holo.Light.Widget.PopupMenu.Large" parent="TextAppearance.Holo.Widget.PopupMenu.Large"> + </style> + + <style name="TextAppearance.Holo.Light.Widget.PopupMenu.Small" parent="TextAppearance.Holo.Widget.PopupMenu.Small"> + </style> + + <style name="TextAppearance.Holo.Light.Widget.DropDownHint" parent="TextAppearance.Holo.Widget.DropDownHint"> + </style> + + <style name="TextAppearance.Holo.Light.Widget.ActionMode.Title" parent="TextAppearance.Widget.ActionMode.Title"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="TextAppearance.Holo.Light.Widget.ActionMode.Subtitle" parent="TextAppearance.Widget.ActionMode.Subtitle"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="TextAppearance.Holo.Light.WindowTitle"> + <item name="android:textColor">#fff</item> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">bold</item> + </style> + + <style name="TextAppearance.Holo.Light.DialogWindowTitle"> + <item name="android:textSize">18sp</item> + </style> + + <!-- Widget Styles --> + + <style name="Widget.Holo" parent="Widget"> + </style> + + <style name="Widget.Holo.Button" parent="Widget.Button"> + <item name="android:background">@android:drawable/btn_default_holo_dark</item> + <item name="android:textAppearance">?android:attr/textAppearanceMedium</item> + <item name="android:textColor">@android:color/primary_text_holo_dark</item> + <item name="android:minHeight">48dip</item> + <item name="android:paddingLeft">32dip</item> + <item name="android:paddingRight">32dip</item> + </style> + + <style name="Widget.Holo.Button.Small"> + <item name="android:background">@android:drawable/btn_default_holo_dark</item> + <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:textColor">@android:color/primary_text_holo_dark</item> + <item name="android:minHeight">40dip</item> + <item name="android:paddingLeft">24dip</item> + <item name="android:paddingRight">24dip</item> + </style> + + <style name="Widget.Holo.Button.Inset"> + <item name="android:background">@android:drawable/button_inset</item> + </style> + + <style name="Widget.Holo.Button.Toggle"> + <item name="android:background">@android:drawable/btn_toggle_holo_dark</item> + <item name="android:textOn">@android:string/capital_on</item> + <item name="android:textOff">@android:string/capital_off</item> + <item name="android:disabledAlpha">?android:attr/disabledAlpha</item> + <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:minHeight">48dip</item> + <item name="android:paddingLeft">24dip</item> + <item name="android:paddingRight">24dip</item> + </style> + + <style name="Widget.Holo.TextView" parent="Widget.TextView"> + </style> + + <style name="Widget.Holo.TextView.ListSeparator" parent="Widget.TextView.ListSeparator"> + </style> + + <style name="Widget.Holo.TextSelectHandle" parent="Widget.TextSelectHandle"> + </style> + + <style name="Widget.Holo.AbsListView" parent="Widget.AbsListView"> + </style> + + <style name="Widget.Holo.AutoCompleteTextView" parent="Widget.AutoCompleteTextView"> + </style> + + <style name="Widget.Holo.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox"> + </style> + + <style name="Widget.Holo.ListView.DropDown"> + </style> + + <style name="Widget.Holo.EditText" parent="Widget.EditText"> + </style> + + <style name="Widget.Holo.ExpandableListView" parent="Widget.ExpandableListView"> + </style> + + <style name="Widget.Holo.ExpandableListView.White"> + </style> + + <style name="Widget.Holo.Gallery" parent="Widget.Gallery"> + </style> + + <style name="Widget.Holo.GestureOverlayView" parent="Widget.GestureOverlayView"> + </style> + + <style name="Widget.Holo.GridView" parent="Widget.GridView"> + </style> + + <style name="Widget.Holo.ImageButton" parent="Widget.ImageButton"> + </style> + + <style name="Widget.Holo.ImageWell" parent="Widget.ImageWell"> + </style> + + <style name="Widget.Holo.ListView" parent="Widget.ListView"> + </style> + + <style name="Widget.Holo.ListView.White"> + </style> + + <style name="Widget.Holo.PopupWindow" parent="Widget.PopupWindow"> + </style> + + <style name="Widget.Holo.ProgressBar" parent="Widget.ProgressBar"> + </style> + + <style name="Widget.Holo.ProgressBar.Horizontal" parent="Widget.ProgressBar.Horizontal"> + </style> + + <style name="Widget.Holo.ProgressBar.Small" parent="Widget.ProgressBar.Horizontal"> + </style> + + <style name="Widget.Holo.ProgressBar.Small.Title" parent="Widget.ProgressBar.Small.Title"> + </style> + + <style name="Widget.Holo.ProgressBar.Large" parent="Widget.ProgressBar.Large"> + </style> + + <style name="Widget.Holo.ProgressBar.Inverse" parent="Widget.ProgressBar.Inverse"> + </style> + + <style name="Widget.Holo.ProgressBar.Small.Inverse" parent="Widget.ProgressBar.Small.Inverse"> + </style> + + <style name="Widget.Holo.ProgressBar.Large.Inverse" parent="Widget.ProgressBar.Large.Inverse"> + </style> + + <style name="Widget.Holo.SeekBar" parent="Widget.SeekBar"> + </style> + + <style name="Widget.Holo.RatingBar" parent="Widget.RatingBar"> + </style> + + <style name="Widget.Holo.RatingBar.Indicator" parent="Widget.RatingBar.Indicator"> + </style> + + <style name="Widget.Holo.RatingBar.Small"> + </style> + + <style name="Widget.Holo.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton"> + </style> + + <style name="Widget.Holo.ScrollView" parent="Widget.ScrollView"> + </style> + + <style name="Widget.Holo.HorizontalScrollView" parent="Widget.HorizontalScrollView"> + </style> + + <style name="Widget.Holo.Spinner" parent="Widget.Spinner.DropDown"> + </style> + + <style name="Widget.Holo.Spinner.DropDown"> + </style> + + <style name="Widget.Holo.CompoundButton.Star" parent="Widget.CompoundButton.Star"> + </style> + + <style name="Widget.Holo.TabWidget" parent="Widget.TabWidget"> + </style> + + <style name="Widget.Holo.WebTextView" parent="Widget.WebTextView"> + </style> + + <style name="Widget.Holo.WebView" parent="Widget.WebView"> + </style> + + <style name="Widget.Holo.DropDownItem" parent="Widget.DropDownItem"> + </style> + + <style name="Widget.Holo.DropDownItem.Spinner"> + </style> + + <style name="Widget.Holo.TextView.SpinnerItem" parent="Widget.TextView.SpinnerItem"> + </style> + + <style name="Widget.Holo.KeyboardView" parent="Widget.KeyboardView"> + </style> + + <style name="Widget.Holo.QuickContactBadge.WindowSmall" parent="Widget.QuickContactBadge.WindowSmall"> + </style> + + <style name="Widget.Holo.QuickContactBadge.WindowMedium" parent="Widget.QuickContactBadge.WindowMedium"> + </style> + + <style name="Widget.Holo.QuickContactBadge.WindowLarge" parent="Widget.QuickContactBadge.WindowLarge"> + </style> + + <style name="Widget.Holo.QuickContactBadgeSmall.WindowSmall" parent="Widget.QuickContactBadgeSmall.WindowSmall"> + </style> + + <style name="Widget.Holo.QuickContactBadgeSmall.WindowMedium" parent="Widget.QuickContactBadgeSmall.WindowMedium"> + </style> + + <style name="Widget.Holo.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"> + </style> + + <style name="Widget.Holo.ListPopupWindow" parent="Widget.ListPopupWindow"> + </style> + + <style name="Widget.Holo.PopupMenu" parent="Widget.PopupMenu"> + </style> + + <style name="Widget.Holo.ActionButton" parent="Widget.ActionButton"> + </style> + + <style name="Widget.Holo.ActionButton.Overflow" parent="Widget.ActionButton.Overflow"> + </style> + + <style name="Widget.Holo.ActionBarView_TabView" parent="Widget.ActionBarView_TabView"> + </style> + + <style name="Widget.Holo.ActionBarView_TabBar" parent="Widget.ActionBarView_TabBar"> + </style> + + <style name="Widget.Holo.ActionBarView_TabText" parent="Widget.ActionBarView_TabText"> + <item name="android:textAppearance">@style/TextAppearance.Holo.Medium</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">18sp</item> + </style> + + <style name="Widget.Holo.ActionMode" parent="Widget.ActionMode"> + <item name="android:background">@android:drawable/cab_holo_dark</item> + <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Title</item> + <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Subtitle</item> + </style> + + <style name="Widget.Holo.ActionButton.CloseMode" parent="Widget.ActionButton.CloseMode"> + <item name="android:src">@drawable/cab_ic_close_holo</item> + </style> + + <style name="Widget.Holo.ActionBar" parent="Widget.ActionBar"> + <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item> + <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle</item> + </style> + + <!-- Light widget styles --> + + <style name="Widget.Holo.Light"> + </style> + + <style name="Widget.Holo.Light.Button" parent="Widget.Button"> + </style> + + <style name="Widget.Holo.Light.Button.Small"> + </style> + + <style name="Widget.Holo.Light.Button.Inset"> + </style> + + <style name="Widget.Holo.Light.Button.Toggle"> + <item name="android:background">@android:drawable/btn_toggle_holo_light</item> + <item name="android:textOn">@android:string/capital_on</item> + <item name="android:textOff">@android:string/capital_off</item> + <item name="android:disabledAlpha">?android:attr/disabledAlpha</item> + <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:minHeight">48dip</item> + <item name="android:paddingLeft">24dip</item> + <item name="android:paddingRight">24dip</item> + </style> + + <style name="Widget.Holo.Light.TextView" parent="Widget.TextView"> + </style> + + <style name="Widget.Holo.Light.TextView.ListSeparator" parent="Widget.TextView.ListSeparator"> + </style> + + <style name="Widget.Holo.Light.TextSelectHandle" parent="Widget.TextSelectHandle"> + </style> + + <style name="Widget.Holo.Light.AbsListView" parent="Widget.AbsListView"> + </style> + + <style name="Widget.Holo.Light.AutoCompleteTextView" parent="Widget.AutoCompleteTextView"> + </style> + + <style name="Widget.Holo.Light.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox"> + </style> + + <style name="Widget.Holo.Light.ListView.DropDown"> + </style> + + <style name="Widget.Holo.Light.EditText" parent="Widget.EditText"> + </style> + + <style name="Widget.Holo.Light.ExpandableListView" parent="Widget.ExpandableListView"> + </style> + + <style name="Widget.Holo.Light.ExpandableListView.White"> + </style> + + <style name="Widget.Holo.Light.Gallery" parent="Widget.Gallery"> + </style> + + <style name="Widget.Holo.Light.GestureOverlayView" parent="Widget.GestureOverlayView"> + </style> + + <style name="Widget.Holo.Light.GridView" parent="Widget.GridView"> + </style> + + <style name="Widget.Holo.Light.ImageButton" parent="Widget.ImageButton"> + </style> + + <style name="Widget.Holo.Light.ImageWell" parent="Widget.ImageWell"> + </style> + + <style name="Widget.Holo.Light.ListView" parent="Widget.ListView"> + </style> + + <style name="Widget.Holo.Light.ListView.White"> + </style> + + <style name="Widget.Holo.Light.PopupWindow" parent="Widget.PopupWindow"> + </style> + + <style name="Widget.Holo.Light.ProgressBar" parent="Widget.ProgressBar"> + </style> + + <style name="Widget.Holo.Light.ProgressBar.Horizontal" parent="Widget.ProgressBar.Horizontal"> + </style> + + <style name="Widget.Holo.Light.ProgressBar.Small" parent="Widget.ProgressBar.Small"> + </style> + + <style name="Widget.Holo.Light.ProgressBar.Small.Title" parent="Widget.ProgressBar.Small.Title"> + </style> + + <style name="Widget.Holo.Light.ProgressBar.Large" parent="Widget.ProgressBar.Large"> + </style> + + <style name="Widget.Holo.Light.ProgressBar.Inverse" parent="Widget.ProgressBar.Inverse"> + </style> + + <style name="Widget.Holo.Light.ProgressBar.Small.Inverse" parent="Widget.ProgressBar.Small.Inverse"> + </style> + + <style name="Widget.Holo.Light.ProgressBar.Large.Inverse" parent="Widget.ProgressBar.Large.Inverse"> + </style> + + <style name="Widget.Holo.Light.SeekBar" parent="Widget.SeekBar"> + </style> + + <style name="Widget.Holo.Light.RatingBar" parent="Widget.RatingBar"> + </style> + + <style name="Widget.Holo.Light.RatingBar.Indicator" parent="Widget.RatingBar.Indicator"> + </style> + + <style name="Widget.Holo.Light.RatingBar.Small"> + </style> + + <style name="Widget.Holo.Light.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton"> + </style> + + <style name="Widget.Holo.Light.ScrollView" parent="Widget.ScrollView"> + </style> + + <style name="Widget.Holo.Light.HorizontalScrollView" parent="Widget.HorizontalScrollView"> + </style> + + <style name="Widget.Holo.Light.Spinner" parent="Widget.Spinner.DropDown"> + </style> + + <style name="Widget.Holo.Light.Spinner.DropDown"> + </style> + + <style name="Widget.Holo.Light.CompoundButton.Star" parent="Widget.CompoundButton.Star"> + </style> + + <style name="Widget.Holo.Light.TabWidget" parent="Widget.TabWidget"> + </style> + + <style name="Widget.Holo.Light.WebTextView" parent="Widget.WebTextView"> + </style> + + <style name="Widget.Holo.Light.WebView" parent="Widget.WebView"> + </style> + + <style name="Widget.Holo.Light.DropDownItem" parent="Widget.DropDownItem"> + </style> + + <style name="Widget.Holo.Light.DropDownItem.Spinner"> + </style> + + <style name="Widget.Holo.Light.TextView.SpinnerItem" parent="Widget.TextView.SpinnerItem"> + </style> + + <style name="Widget.Holo.Light.KeyboardView" parent="Widget.KeyboardView"> + </style> + + <style name="Widget.Holo.Light.QuickContactBadge.WindowSmall" parent="Widget.QuickContactBadge.WindowSmall"> + </style> + + <style name="Widget.Holo.Light.QuickContactBadge.WindowMedium" parent="Widget.QuickContactBadge.WindowMedium"> + </style> + + <style name="Widget.Holo.Light.QuickContactBadge.WindowLarge" parent="Widget.QuickContactBadge.WindowLarge"> + </style> + + <style name="Widget.Holo.Light.QuickContactBadgeSmall.WindowSmall" parent="Widget.QuickContactBadgeSmall.WindowSmall"> + </style> + + <style name="Widget.Holo.Light.QuickContactBadgeSmall.WindowMedium" parent="Widget.QuickContactBadgeSmall.WindowMedium"> + </style> + + <style name="Widget.Holo.Light.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"> + </style> + + <style name="Widget.Holo.Light.ListPopupWindow" parent="Widget.ListPopupWindow"> + </style> + + <style name="Widget.Holo.Light.PopupMenu" parent="Widget.PopupMenu"> + </style> + + <style name="Widget.Holo.Light.ActionButton" parent="Widget.ActionButton"> + </style> + + <style name="Widget.Holo.Light.ActionButton.Overflow" parent="Widget.ActionButton.Overflow"> + </style> + + <style name="Widget.Holo.Light.ActionBarView_TabView" parent="Widget.ActionBarView_TabView"> + </style> + + <style name="Widget.Holo.Light.ActionBarView_TabBar" parent="Widget.ActionBarView_TabBar"> + </style> + + <style name="Widget.Holo.Light.ActionBarView_TabText" parent="Widget.ActionBarView_TabText"> + </style> + + <style name="Widget.Holo.Light.ActionMode" parent="Widget.ActionMode"> + <item name="android:background">@android:drawable/cab_holo_light</item> + <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Title</item> + <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionMode.Subtitle</item> + </style> + + <style name="Widget.Holo.Light.ActionButton.CloseMode" parent="Widget.ActionButton.CloseMode"> + </style> + + <style name="Widget.Holo.Light.ActionBar" parent="Widget.ActionBar"> + <item name="android:titleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item> + <item name="android:subtitleTextStyle">@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle</item> + </style> + + <!-- Animation Styles --> + + <style name="Animation.Holo" parent="Animation"> + </style> + + <style name="Animation.Holo.Activity" parent="Animation.Activity"> + </style> + + <style name="Animation.Holo.Dialog" parent="Animation.Dialog"> + </style> + + <!-- Dialog styles --> + + <style name="AlertDialog.Holo" parent="AlertDialog"> + <item name="fullDark">@android:drawable/dialog_full_holo</item> + <item name="topDark">@android:drawable/dialog_top_holo</item> + <item name="centerDark">@android:drawable/dialog_middle_holo</item> + <item name="bottomDark">@android:drawable/dialog_bottom_holo</item> + <item name="fullBright">@android:drawable/dialog_full_holo</item> + <item name="topBright">@android:drawable/dialog_top_holo</item> + <item name="centerBright">@android:drawable/dialog_middle_holo</item> + <item name="bottomBright">@android:drawable/dialog_bottom_holo</item> + <item name="bottomMedium">@android:drawable/dialog_bottom_holo</item> + <item name="centerMedium">@android:drawable/dialog_middle_holo</item> + </style> + + <!-- Window title --> + <style name="WindowTitleBackground.Holo"> + <item name="android:background">@android:drawable/title_bar</item> + </style> + + <style name="WindowTitle.Holo"> + <item name="android:singleLine">true</item> + <item name="android:textAppearance">@style/TextAppearance.Holo.WindowTitle</item> + <item name="android:shadowColor">#BB000000</item> + <item name="android:shadowRadius">2.75</item> + </style> + + <style name="DialogWindowTitle.Holo"> + <item name="android:maxLines">1</item> + <item name="android:scrollHorizontally">true</item> + <item name="android:textAppearance">@style/TextAppearance.Holo.DialogWindowTitle</item> + </style> + </resources> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 9425fc4..21d91ba 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -57,6 +57,7 @@ <item name="textColorHighlightInverse">@android:color/highlighted_text_light</item> <item name="textColorLink">@android:color/link_text_dark</item> <item name="textColorLinkInverse">@android:color/link_text_light</item> + <item name="textColorAlertDialogListItem">@android:color/primary_text_light_disable_only</item> <item name="textAppearanceLarge">@android:style/TextAppearance.Large</item> <item name="textAppearanceMedium">@android:style/TextAppearance.Medium</item> @@ -102,6 +103,8 @@ <item name="activatedBackgroundIndicator">@android:drawable/activated_background</item> + <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_bright</item> + <item name="expandableListPreferredItemPaddingLeft">40dip</item> <item name="expandableListPreferredChildPaddingLeft"> ?android:attr/expandableListPreferredItemPaddingLeft</item> @@ -606,14 +609,230 @@ holographic theme are translucent on their brackground, so applications must ensure that any background they use with this theme is itself dark; otherwise, it will be difficult to see the widgets. The new - UI style also includes a full action bar by default. --> + UI style also includes a full action bar by default. + + Styles used by the Holo theme are named using the convention Type.Holo.Etc. + (For example, Widget.Holo.Button, TextAppearance.Holo.Widget.PopupMenu.Large.) + Specific resources used by Holo are named using the convention @type/foo_bar_baz_holo + with trailing _dark or _light specifiers if they are not shared between both light and + dark versions of the theme. --> <style name="Theme.Holo"> - <item name="editTextBackground">@android:drawable/edit_text_holo_dark</item> + <item name="colorForeground">@android:color/bright_foreground_holo_dark</item> + <item name="colorForegroundInverse">@android:color/bright_foreground_inverse_holo_dark</item> + <item name="colorBackground">@android:color/background_holo_dark</item> + <item name="colorBackgroundCacheHint">?android:attr/colorBackground</item> + <item name="disabledAlpha">0.5</item> + <item name="backgroundDimAmount">0.6</item> + + <!-- Text styles --> + <item name="textAppearance">@android:style/TextAppearance.Holo</item> + <item name="textAppearanceInverse">@android:style/TextAppearance.Holo.Inverse</item> + + <item name="textColorPrimary">@android:color/primary_text_holo_dark</item> + <item name="textColorSecondary">@android:color/secondary_text_holo_dark</item> + <item name="textColorTertiary">@android:color/tertiary_text_holo_dark</item> + <item name="textColorPrimaryInverse">@android:color/primary_text_holo_light</item> + <item name="textColorSecondaryInverse">@android:color/secondary_text_holo_light</item> + <item name="textColorTertiaryInverse">@android:color/tertiary_text_holo_light</item> + <item name="textColorPrimaryDisableOnly">@android:color/primary_text_disable_only_holo_dark</item> + <item name="textColorPrimaryInverseDisableOnly">@android:color/primary_text_disable_only_holo_light</item> + <item name="textColorPrimaryNoDisable">@android:color/primary_text_nodisable_holo_dark</item> + <item name="textColorSecondaryNoDisable">@android:color/secondary_text_nodisable_holo_dark</item> + <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_nodisable_holo_light</item> + <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_nodisable_holo_light</item> + <item name="textColorHint">@android:color/hint_foreground_holo_dark</item> + <item name="textColorHintInverse">@android:color/hint_foreground_holo_light</item> + <item name="textColorSearchUrl">@android:color/search_url_text_holo</item> + <item name="textColorHighlight">@android:color/highlighted_text_holo_dark</item> + <item name="textColorHighlightInverse">@android:color/highlighted_text_holo_light</item> + <item name="textColorLink">@android:color/link_text_holo_dark</item> + <item name="textColorLinkInverse">@android:color/link_text_holo_light</item> + <item name="textColorAlertDialogListItem">@android:color/primary_text_holo_dark</item> + + <item name="textAppearanceLarge">@android:style/TextAppearance.Holo.Large</item> + <item name="textAppearanceMedium">@android:style/TextAppearance.Holo.Medium</item> + <item name="textAppearanceSmall">@android:style/TextAppearance.Holo.Small</item> + <item name="textAppearanceLargeInverse">@android:style/TextAppearance.Holo.Large.Inverse</item> + <item name="textAppearanceMediumInverse">@android:style/TextAppearance.Holo.Medium.Inverse</item> + <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Holo.Small.Inverse</item> + <item name="textAppearanceSearchResultTitle">@android:style/TextAppearance.Holo.SearchResult.Title</item> + <item name="textAppearanceSearchResultSubtitle">@android:style/TextAppearance.Holo.SearchResult.Subtitle</item> + + <item name="textAppearanceButton">@android:style/TextAppearance.Holo.Widget.Button</item> + <item name="editTextColor">?android:attr/textColorPrimary</item> - <item name="android:windowActionBar">true</item> - <item name="android:spinnerStyle">?android:attr/dropDownSpinnerStyle</item> + <item name="editTextBackground">@android:drawable/edit_text_holo_dark</item> + + <item name="candidatesTextStyleSpans">@android:string/candidates_style</item> + + <item name="textCheckMark">@android:drawable/indicator_check_mark_dark</item> + <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_light</item> + + <item name="textAppearanceLargePopupMenu">@android:style/TextAppearance.Holo.Widget.PopupMenu.Large</item> + <item name="textAppearanceSmallPopupMenu">@android:style/TextAppearance.Holo.Widget.PopupMenu.Small</item> + + <!-- Button styles --> + <item name="buttonStyle">@android:style/Widget.Holo.Button</item> + + <item name="buttonStyleSmall">@android:style/Widget.Holo.Button.Small</item> + <item name="buttonStyleInset">@android:style/Widget.Holo.Button.Inset</item> + + <item name="buttonStyleToggle">@android:style/Widget.Holo.Button.Toggle</item> + + <!-- List attributes --> + <item name="listPreferredItemHeight">64dip</item> + <!-- @hide --> + <item name="searchResultListItemHeight">58dip</item> + <item name="listDivider">@drawable/divider_horizontal_holo_dark</item> + <item name="listSeparatorTextViewStyle">@android:style/Widget.Holo.TextView.ListSeparator</item> + <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_holo_dark</item> <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check_holo_dark</item> + + <item name="listChoiceBackgroundIndicator">@android:drawable/list_selected_background</item> + + <item name="activatedBackgroundIndicator">@android:drawable/activated_background</item> + + <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_holo_dark</item> + + <item name="expandableListPreferredItemPaddingLeft">40dip</item> + <item name="expandableListPreferredChildPaddingLeft"> + ?android:attr/expandableListPreferredItemPaddingLeft</item> + + <item name="expandableListPreferredItemIndicatorLeft">3dip</item> + <item name="expandableListPreferredItemIndicatorRight">33dip</item> + <item name="expandableListPreferredChildIndicatorLeft"> + ?android:attr/expandableListPreferredItemIndicatorLeft</item> + <item name="expandableListPreferredChildIndicatorRight"> + ?android:attr/expandableListPreferredItemIndicatorRight</item> + + <!-- Gallery attributes --> + <item name="galleryItemBackground">@android:drawable/gallery_item_background</item> + + <!-- Window attributes --> + <item name="windowBackground">@android:drawable/screen_background_holo_dark</item> + <item name="windowFrame">@null</item> + <item name="windowNoTitle">false</item> + <item name="windowFullscreen">false</item> + <item name="windowIsFloating">false</item> + <item name="windowContentOverlay">@android:drawable/title_bar_shadow</item> + <item name="windowShowWallpaper">false</item> + <item name="windowTitleStyle">@android:style/WindowTitle.Holo</item> + <item name="windowTitleSize">25dip</item> + <item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground.Holo</item> + <item name="android:windowAnimationStyle">@android:style/Animation.Holo.Activity</item> + <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item> + <item name="windowActionBar">true</item> + <item name="windowActionModeOverlay">false</item> + + <!-- Dialog attributes --> + <item name="alertDialogStyle">@android:style/AlertDialog.Holo</item> + + <!-- Panel attributes --> + <item name="panelBackground">@android:drawable/menu_background</item> + <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item> + <!-- These three attributes do not seems to be used by the framework. Declared public though --> + <item name="panelColorBackground">#000</item> + <item name="panelColorForeground">?android:attr/textColorPrimary</item> + <item name="panelTextAppearance">?android:attr/textAppearance</item> + + <!-- Scrollbar attributes --> + <item name="scrollbarFadeDuration">250</item> + <item name="scrollbarDefaultDelayBeforeFade">300</item> + <item name="scrollbarSize">10dip</item> + <item name="scrollbarThumbHorizontal">@android:drawable/scrollbar_handle_horizontal</item> + <item name="scrollbarThumbVertical">@android:drawable/scrollbar_handle_vertical</item> + <item name="scrollbarTrackHorizontal">@null</item> + <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="textSelectHandle">@android:drawable/text_select_handle_middle</item> + <item name="textSelectHandleWindowStyle">@android:style/Widget.Holo.TextSelectHandle</item> + + <!-- Widget styles --> + <item name="absListViewStyle">@android:style/Widget.Holo.AbsListView</item> + <item name="autoCompleteTextViewStyle">@android:style/Widget.Holo.AutoCompleteTextView</item> + <item name="checkboxStyle">@android:style/Widget.Holo.CompoundButton.CheckBox</item> + <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item> + <item name="editTextStyle">@android:style/Widget.Holo.EditText</item> + <item name="expandableListViewStyle">@android:style/Widget.Holo.ExpandableListView</item> + <item name="expandableListViewWhiteStyle">@android:style/Widget.Holo.ExpandableListView.White</item> + <item name="galleryStyle">@android:style/Widget.Holo.Gallery</item> + <item name="gestureOverlayViewStyle">@android:style/Widget.Holo.GestureOverlayView</item> + <item name="gridViewStyle">@android:style/Widget.Holo.GridView</item> + <item name="imageButtonStyle">@android:style/Widget.Holo.ImageButton</item> + <item name="imageWellStyle">@android:style/Widget.Holo.ImageWell</item> + <item name="listViewStyle">@android:style/Widget.Holo.ListView</item> + <item name="listViewWhiteStyle">@android:style/Widget.Holo.ListView.White</item> + <item name="popupWindowStyle">@android:style/Widget.Holo.PopupWindow</item> + <item name="progressBarStyle">@android:style/Widget.Holo.ProgressBar</item> + <item name="progressBarStyleHorizontal">@android:style/Widget.Holo.ProgressBar.Horizontal</item> + <item name="progressBarStyleSmall">@android:style/Widget.Holo.ProgressBar.Small</item> + <item name="progressBarStyleSmallTitle">@android:style/Widget.Holo.ProgressBar.Small.Title</item> + <item name="progressBarStyleLarge">@android:style/Widget.Holo.ProgressBar.Large</item> + <item name="progressBarStyleInverse">@android:style/Widget.Holo.ProgressBar.Inverse</item> + <item name="progressBarStyleSmallInverse">@android:style/Widget.Holo.ProgressBar.Small.Inverse</item> + <item name="progressBarStyleLargeInverse">@android:style/Widget.Holo.ProgressBar.Large.Inverse</item> + <item name="seekBarStyle">@android:style/Widget.Holo.SeekBar</item> + <item name="ratingBarStyle">@android:style/Widget.Holo.RatingBar</item> + <item name="ratingBarStyleIndicator">@android:style/Widget.Holo.RatingBar.Indicator</item> + <item name="ratingBarStyleSmall">@android:style/Widget.Holo.RatingBar.Small</item> + <item name="radioButtonStyle">@android:style/Widget.Holo.CompoundButton.RadioButton</item> + <item name="scrollViewStyle">@android:style/Widget.Holo.ScrollView</item> + <item name="horizontalScrollViewStyle">@android:style/Widget.Holo.HorizontalScrollView</item> + <item name="spinnerStyle">?android:attr/dropDownSpinnerStyle</item> + <item name="dropDownSpinnerStyle">@android:style/Widget.Holo.Spinner.DropDown</item> + <item name="starStyle">@android:style/Widget.Holo.CompoundButton.Star</item> + <item name="tabWidgetStyle">@android:style/Widget.Holo.TabWidget</item> + <item name="textViewStyle">@android:style/Widget.Holo.TextView</item> + <item name="webTextViewStyle">@android:style/Widget.Holo.WebTextView</item> + <item name="webViewStyle">@android:style/Widget.Holo.WebView</item> + <item name="dropDownItemStyle">@android:style/Widget.Holo.DropDownItem</item> + <item name="spinnerDropDownItemStyle">@android:style/Widget.Holo.DropDownItem.Spinner</item> + <item name="spinnerItemStyle">@android:style/Widget.Holo.TextView.SpinnerItem</item> + <item name="dropDownHintAppearance">@android:style/TextAppearance.Holo.Widget.DropDownHint</item> + <item name="keyboardViewStyle">@android:style/Widget.Holo.KeyboardView</item> + <item name="quickContactBadgeStyleWindowSmall">@android:style/Widget.Holo.QuickContactBadge.WindowSmall</item> + <item name="quickContactBadgeStyleWindowMedium">@android:style/Widget.Holo.QuickContactBadge.WindowMedium</item> + <item name="quickContactBadgeStyleWindowLarge">@android:style/Widget.Holo.QuickContactBadge.WindowLarge</item> + <item name="quickContactBadgeStyleSmallWindowSmall">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowSmall</item> + <item name="quickContactBadgeStyleSmallWindowMedium">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowMedium</item> + <item name="quickContactBadgeStyleSmallWindowLarge">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowLarge</item> + <item name="listPopupWindowStyle">@android:style/Widget.Holo.ListPopupWindow</item> + <item name="popupMenuStyle">@android:style/Widget.Holo.PopupMenu</item> + + <!-- Preference styles --> + <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item> + <item name="preferenceCategoryStyle">@android:style/Preference.Category</item> + <item name="preferenceStyle">@android:style/Preference</item> + <item name="preferenceInformationStyle">@android:style/Preference.Information</item> + <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item> + <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item> + <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item> + <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item> + <item name="ringtonePreferenceStyle">@android:style/Preference.RingtonePreference</item> + <item name="preferenceLayoutChild">@android:layout/preference_child</item> + + <!-- Search widget styles --> + <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item> + + <!-- Action bar styles --> + <item name="actionDropDownStyle">@android:style/Widget.Holo.Spinner.DropDown</item> + <item name="actionButtonStyle">@android:style/Widget.Holo.ActionButton</item> + <item name="actionOverflowButtonStyle">@android:style/Widget.Holo.ActionButton.Overflow</item> + <item name="actionButtonPadding">12dip</item> + <item name="actionModeBackground">@android:drawable/cab_holo_dark</item> + <item name="actionModeCloseDrawable">@android:drawable/cab_ic_close_holo</item> + <item name="actionBarTabStyle">@style/Widget.Holo.ActionBarView_TabView</item> + <item name="actionBarTabBarStyle">@style/Widget.Holo.ActionBarView_TabBar</item> + <item name="actionBarTabTextStyle">@style/Widget.Holo.ActionBarView_TabText</item> + <item name="actionModeStyle">@style/Widget.Holo.ActionMode</item> + <item name="actionModeCloseButtonStyle">@style/Widget.Holo.ActionButton.CloseMode</item> + <item name="actionBarStyle">@android:style/Widget.Holo.ActionBar</item> + <item name="actionBarSize">50dip</item> + </style> <!-- New Honeycomb holographic theme. Light version. The widgets in the @@ -621,14 +840,229 @@ must ensure that any background they use with this theme is itself light; otherwise, it will be difficult to see the widgets. The new UI style also includes a full action bar by default. --> - <style name="Theme.Light.Holo"> + <style name="Theme.Holo.Light" parent="Theme.Light"> + <item name="colorForeground">@android:color/bright_foreground_holo_light</item> + <item name="colorForegroundInverse">@android:color/bright_foreground_inverse_holo_light</item> + <item name="colorBackground">@android:color/background_holo_light</item> + <item name="colorBackgroundCacheHint">?android:attr/colorBackground</item> + <item name="disabledAlpha">0.5</item> + <item name="backgroundDimAmount">0.6</item> + + <!-- Text styles --> + <item name="textAppearance">@android:style/TextAppearance.Holo.Light</item> + <item name="textAppearanceInverse">@android:style/TextAppearance.Holo.Light.Inverse</item> + + <item name="textColorPrimary">@android:color/primary_text_holo_light</item> + <item name="textColorSecondary">@android:color/secondary_text_holo_light</item> + <item name="textColorTertiary">@android:color/tertiary_text_holo_light</item> + <item name="textColorPrimaryInverse">@android:color/primary_text_holo_dark</item> + <item name="textColorSecondaryInverse">@android:color/secondary_text_holo_dark</item> + <item name="textColorTertiaryInverse">@android:color/tertiary_text_holo_dark</item> + <item name="textColorPrimaryDisableOnly">@android:color/primary_text_disable_only_holo_light</item> + <item name="textColorPrimaryInverseDisableOnly">@android:color/primary_text_disable_only_holo_dark</item> + <item name="textColorPrimaryNoDisable">@android:color/primary_text_nodisable_holo_light</item> + <item name="textColorSecondaryNoDisable">@android:color/secondary_text_nodisable_holo_light</item> + <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_nodisable_holo_dark</item> + <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_nodisable_holo_dark</item> + <item name="textColorHint">@android:color/hint_foreground_holo_light</item> + <item name="textColorHintInverse">@android:color/hint_foreground_holo_dark</item> + <item name="textColorSearchUrl">@android:color/search_url_text_holo</item> + <item name="textColorHighlight">@android:color/highlighted_text_holo_light</item> + <item name="textColorHighlightInverse">@android:color/highlighted_text_holo_dark</item> + <item name="textColorLink">@android:color/link_text_holo_light</item> + <item name="textColorLinkInverse">@android:color/link_text_holo_dark</item> + <item name="textColorAlertDialogListItem">@android:color/primary_text_holo_dark</item> + + <item name="textAppearanceLarge">@android:style/TextAppearance.Holo.Light.Large</item> + <item name="textAppearanceMedium">@android:style/TextAppearance.Holo.Light.Medium</item> + <item name="textAppearanceSmall">@android:style/TextAppearance.Holo.Light.Small</item> + <item name="textAppearanceLargeInverse">@android:style/TextAppearance.Holo.Light.Large.Inverse</item> + <item name="textAppearanceMediumInverse">@android:style/TextAppearance.Holo.Light.Medium.Inverse</item> + <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Holo.Light.Small.Inverse</item> + <item name="textAppearanceSearchResultTitle">@android:style/TextAppearance.Holo.Light.SearchResult.Title</item> + <item name="textAppearanceSearchResultSubtitle">@android:style/TextAppearance.Holo.Light.SearchResult.Subtitle</item> + + <item name="textAppearanceButton">@android:style/TextAppearance.Holo.Light.Widget.Button</item> + + <item name="editTextColor">?android:attr/textColorPrimary</item> <item name="editTextBackground">@android:drawable/edit_text_holo_light</item> - <item name="android:windowActionBar">true</item> - <item name="android:spinnerStyle">?android:attr/dropDownSpinnerStyle</item> - <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_holo_light</item> - <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check_holo_light</item> - </style> + <item name="candidatesTextStyleSpans">@android:string/candidates_style</item> + + <item name="textCheckMark">@android:drawable/indicator_check_mark_light</item> + <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_dark</item> + + <item name="textAppearanceLargePopupMenu">@android:style/TextAppearance.Holo.Light.Widget.PopupMenu.Large</item> + <item name="textAppearanceSmallPopupMenu">@android:style/TextAppearance.Holo.Light.Widget.PopupMenu.Small</item> + + <!-- Button styles --> + <item name="buttonStyle">@android:style/Widget.Holo.Light.Button</item> + + <item name="buttonStyleSmall">@android:style/Widget.Holo.Light.Button.Small</item> + <item name="buttonStyleInset">@android:style/Widget.Holo.Light.Button.Inset</item> + + <item name="buttonStyleToggle">@android:style/Widget.Holo.Light.Button.Toggle</item> + + <!-- List attributes --> + <item name="listPreferredItemHeight">64dip</item> + <!-- @hide --> + <item name="searchResultListItemHeight">58dip</item> + <item name="listDivider">@drawable/divider_horizontal_holo_dark</item> + <item name="listSeparatorTextViewStyle">@android:style/Widget.Holo.Light.TextView.ListSeparator</item> + + <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item> + <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item> + + <item name="listChoiceBackgroundIndicator">@android:drawable/list_selected_background</item> + + <item name="activatedBackgroundIndicator">@android:drawable/activated_background</item> + + <item name="expandableListPreferredItemPaddingLeft">40dip</item> + <item name="expandableListPreferredChildPaddingLeft"> + ?android:attr/expandableListPreferredItemPaddingLeft</item> + + <item name="expandableListPreferredItemIndicatorLeft">3dip</item> + <item name="expandableListPreferredItemIndicatorRight">33dip</item> + <item name="expandableListPreferredChildIndicatorLeft"> + ?android:attr/expandableListPreferredItemIndicatorLeft</item> + <item name="expandableListPreferredChildIndicatorRight"> + ?android:attr/expandableListPreferredItemIndicatorRight</item> + + <item name="listDividerAlertDialog">@android:drawable/divider_horizontal_holo_dark</item> + + <!-- Gallery attributes --> + <item name="galleryItemBackground">@android:drawable/gallery_item_background</item> + + <!-- Window attributes --> + <item name="windowBackground">@android:drawable/screen_background_holo_light</item> + <item name="windowFrame">@null</item> + <item name="windowNoTitle">false</item> + <item name="windowFullscreen">false</item> + <item name="windowIsFloating">false</item> + <item name="windowContentOverlay">@android:drawable/title_bar_shadow</item> + <item name="windowShowWallpaper">false</item> + <item name="windowTitleStyle">@android:style/WindowTitle.Holo</item> + <item name="windowTitleSize">25dip</item> + <item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground.Holo</item> + <item name="android:windowAnimationStyle">@android:style/Animation.Holo.Activity</item> + <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item> + <item name="windowActionBar">true</item> + <item name="windowActionModeOverlay">false</item> + + <!-- Dialog attributes --> + <item name="alertDialogStyle">@android:style/AlertDialog.Holo</item> + + <!-- Panel attributes --> + <item name="panelBackground">@android:drawable/menu_background</item> + <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item> + <!-- These three attributes do not seems to be used by the framework. Declared public though --> + <item name="panelColorBackground">#000</item> + <item name="panelColorForeground">?android:attr/textColorPrimary</item> + <item name="panelTextAppearance">?android:attr/textAppearance</item> + + <!-- Scrollbar attributes --> + <item name="scrollbarFadeDuration">250</item> + <item name="scrollbarDefaultDelayBeforeFade">300</item> + <item name="scrollbarSize">10dip</item> + <item name="scrollbarThumbHorizontal">@android:drawable/scrollbar_handle_horizontal</item> + <item name="scrollbarThumbVertical">@android:drawable/scrollbar_handle_vertical</item> + <item name="scrollbarTrackHorizontal">@null</item> + <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="textSelectHandle">@android:drawable/text_select_handle_middle</item> + <item name="textSelectHandleWindowStyle">@android:style/Widget.Holo.TextSelectHandle</item> + + <!-- Widget styles --> + <item name="absListViewStyle">@android:style/Widget.Holo.AbsListView</item> + <item name="autoCompleteTextViewStyle">@android:style/Widget.Holo.AutoCompleteTextView</item> + <item name="checkboxStyle">@android:style/Widget.Holo.CompoundButton.CheckBox</item> + <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item> + <item name="editTextStyle">@android:style/Widget.Holo.EditText</item> + <item name="expandableListViewStyle">@android:style/Widget.Holo.ExpandableListView</item> + <item name="expandableListViewWhiteStyle">@android:style/Widget.Holo.ExpandableListView.White</item> + <item name="galleryStyle">@android:style/Widget.Holo.Gallery</item> + <item name="gestureOverlayViewStyle">@android:style/Widget.Holo.GestureOverlayView</item> + <item name="gridViewStyle">@android:style/Widget.Holo.GridView</item> + <item name="imageButtonStyle">@android:style/Widget.Holo.ImageButton</item> + <item name="imageWellStyle">@android:style/Widget.Holo.ImageWell</item> + <item name="listViewStyle">@android:style/Widget.Holo.ListView</item> + <item name="listViewWhiteStyle">@android:style/Widget.Holo.ListView.White</item> + <item name="popupWindowStyle">@android:style/Widget.Holo.PopupWindow</item> + <item name="progressBarStyle">@android:style/Widget.Holo.ProgressBar</item> + <item name="progressBarStyleHorizontal">@android:style/Widget.Holo.ProgressBar.Horizontal</item> + <item name="progressBarStyleSmall">@android:style/Widget.Holo.ProgressBar.Small</item> + <item name="progressBarStyleSmallTitle">@android:style/Widget.Holo.ProgressBar.Small.Title</item> + <item name="progressBarStyleLarge">@android:style/Widget.Holo.ProgressBar.Large</item> + <item name="progressBarStyleInverse">@android:style/Widget.Holo.ProgressBar.Inverse</item> + <item name="progressBarStyleSmallInverse">@android:style/Widget.Holo.ProgressBar.Small.Inverse</item> + <item name="progressBarStyleLargeInverse">@android:style/Widget.Holo.ProgressBar.Large.Inverse</item> + <item name="seekBarStyle">@android:style/Widget.Holo.SeekBar</item> + <item name="ratingBarStyle">@android:style/Widget.Holo.RatingBar</item> + <item name="ratingBarStyleIndicator">@android:style/Widget.Holo.RatingBar.Indicator</item> + <item name="ratingBarStyleSmall">@android:style/Widget.Holo.RatingBar.Small</item> + <item name="radioButtonStyle">@android:style/Widget.Holo.CompoundButton.RadioButton</item> + <item name="scrollViewStyle">@android:style/Widget.Holo.ScrollView</item> + <item name="horizontalScrollViewStyle">@android:style/Widget.Holo.HorizontalScrollView</item> + <item name="spinnerStyle">?android:attr/dropDownSpinnerStyle</item> + <item name="dropDownSpinnerStyle">@android:style/Widget.Holo.Spinner.DropDown</item> + <item name="starStyle">@android:style/Widget.Holo.CompoundButton.Star</item> + <item name="tabWidgetStyle">@android:style/Widget.Holo.TabWidget</item> + <item name="textViewStyle">@android:style/Widget.Holo.TextView</item> + <item name="webTextViewStyle">@android:style/Widget.Holo.WebTextView</item> + <item name="webViewStyle">@android:style/Widget.Holo.WebView</item> + <item name="dropDownItemStyle">@android:style/Widget.Holo.DropDownItem</item> + <item name="spinnerDropDownItemStyle">@android:style/Widget.Holo.DropDownItem.Spinner</item> + <item name="spinnerItemStyle">@android:style/Widget.Holo.TextView.SpinnerItem</item> + <item name="dropDownHintAppearance">@android:style/TextAppearance.Holo.Widget.DropDownHint</item> + <item name="keyboardViewStyle">@android:style/Widget.Holo.KeyboardView</item> + <item name="quickContactBadgeStyleWindowSmall">@android:style/Widget.Holo.QuickContactBadge.WindowSmall</item> + <item name="quickContactBadgeStyleWindowMedium">@android:style/Widget.Holo.QuickContactBadge.WindowMedium</item> + <item name="quickContactBadgeStyleWindowLarge">@android:style/Widget.Holo.QuickContactBadge.WindowLarge</item> + <item name="quickContactBadgeStyleSmallWindowSmall">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowSmall</item> + <item name="quickContactBadgeStyleSmallWindowMedium">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowMedium</item> + <item name="quickContactBadgeStyleSmallWindowLarge">@android:style/Widget.Holo.QuickContactBadgeSmall.WindowLarge</item> + <item name="listPopupWindowStyle">@android:style/Widget.Holo.ListPopupWindow</item> + <item name="popupMenuStyle">@android:style/Widget.Holo.PopupMenu</item> + + <!-- Preference styles --> + <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item> + <item name="preferenceCategoryStyle">@android:style/Preference.Category</item> + <item name="preferenceStyle">@android:style/Preference</item> + <item name="preferenceInformationStyle">@android:style/Preference.Information</item> + <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item> + <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item> + <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item> + <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item> + <item name="ringtonePreferenceStyle">@android:style/Preference.RingtonePreference</item> + <item name="preferenceLayoutChild">@android:layout/preference_child</item> + + <!-- Search widget styles --> + <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item> + + <!-- Action bar styles --> + <item name="actionDropDownStyle">@android:style/Widget.Holo.Spinner.DropDown</item> + <item name="actionButtonStyle">@android:style/Widget.Holo.ActionButton</item> + <item name="actionOverflowButtonStyle">@android:style/Widget.Holo.ActionButton.Overflow</item> + <item name="actionButtonPadding">12dip</item> + <item name="actionModeBackground">@android:drawable/cab_holo_light</item> + <item name="actionModeCloseDrawable">@android:drawable/cab_ic_close_holo</item> + <item name="actionBarTabStyle">@style/Widget.Holo.ActionBarView_TabView</item> + <item name="actionBarTabBarStyle">@style/Widget.Holo.ActionBarView_TabBar</item> + <item name="actionBarTabTextStyle">@style/Widget.Holo.ActionBarView_TabText</item> + <item name="actionModeStyle">@style/Widget.Holo.Light.ActionMode</item> + <item name="actionModeCloseButtonStyle">@style/Widget.Holo.ActionButton.CloseMode</item> + <item name="actionBarStyle">@android:style/Widget.Holo.ActionBar</item> + <item name="actionBarSize">50dip</item> + + </style> + + <!-- Development legacy name; if you're using this, switch. --> + <style name="Theme.Light.Holo" parent="Theme.Holo.Light"> + </style> + <!-- Variant of the holographic (dark) theme with no action bar. --> <style name="Theme.Holo.NoActionBar"> <item name="android:windowActionBar">false</item> @@ -655,4 +1089,53 @@ <item name="android:windowContentOverlay">@null</item> </style> + <!-- Dialog themes for Holo --> + + <!-- Holo theme for dialog windows and activities, which is used by the + {@link android.app.Dialog} class. This changes the window to be + floating (not fill the entire screen), and puts a frame around its + contents. You can set this theme on an activity if you would like to + make an activity that looks like a Dialog. + This is the default Dialog theme for applications targeting Honeycomb + or newer. --> + <style name="Theme.Holo.Dialog"> + <item name="android:windowFrame">@null</item> + <item name="android:windowTitleStyle">@android:style/DialogWindowTitle.Holo</item> + <item name="android:windowBackground">@android:drawable/dialog_full_holo</item> + <item name="android:windowIsFloating">true</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowAnimationStyle">@android:style/Animation.Holo.Dialog</item> + <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item> + <item name="android:windowActionBar">false</item> + + <item name="android:colorBackgroundCacheHint">@null</item> + + <item name="textAppearance">@android:style/TextAppearance.Holo</item> + <item name="textAppearanceInverse">@android:style/TextAppearance.Holo.Inverse</item> + </style> + + <!-- Variation of Theme.Holo.Dialog that does not include a frame (or background). + The view hierarchy of the dialog is responsible for drawing all of + its pixels. --> + <style name="Theme.Holo.Dialog.NoFrame"> + <item name="windowBackground">@android:color/transparent</item> + <item name="android:windowFrame">@null</item> + <item name="windowContentOverlay">@null</item> + <item name="android:windowAnimationStyle">@null</item> + <item name="android:backgroundDimEnabled">false</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowNoTitle">true</item> + </style> + + <!-- Holo theme for alert dialog windows, which is used by the + {@link android.app.AlertDialog} class. This is basically a dialog + but sets the background to empty so it can do two-tone backgrounds. + For applications targeting Honeycomb or newer, this is the default + AlertDialog theme. --> + <style name="Theme.Holo.Dialog.Alert"> + <item name="windowBackground">@android:color/transparent</item> + <item name="windowTitleStyle">@android:style/DialogWindowTitle.Holo</item> + <item name="windowContentOverlay">@null</item> + </style> + </resources> diff --git a/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java index a7ec7d5..ed42e64 100644 --- a/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package android.net; +package android.app; +import android.app.DownloadManager.Query; +import android.app.DownloadManager.Request; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.net.ConnectivityManager; -import android.net.DownloadManager; import android.net.NetworkInfo; -import android.net.DownloadManager.Query; -import android.net.DownloadManager.Request; +import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Environment; diff --git a/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java index a61f02d..38f336e 100644 --- a/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package android.net; +package android.app; +import android.app.DownloadManager.Query; +import android.app.DownloadManager.Request; +import android.app.DownloadManagerBaseTest.DataType; +import android.app.DownloadManagerBaseTest.MultipleDownloadsCompletedReceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; -import android.net.DownloadManager; -import android.net.DownloadManager.Query; -import android.net.DownloadManager.Request; -import android.net.DownloadManagerBaseTest.DataType; -import android.net.DownloadManagerBaseTest.MultipleDownloadsCompletedReceiver; +import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Environment; import android.os.ParcelFileDescriptor; diff --git a/core/tests/coretests/src/android/net/DownloadManagerStressTest.java b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java index 9fa8620..4ff0295 100644 --- a/core/tests/coretests/src/android/net/DownloadManagerStressTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java @@ -14,14 +14,15 @@ * limitations under the License. */ -package android.net; +package android.app; import java.io.File; import java.util.Random; +import android.app.DownloadManager.Query; +import android.app.DownloadManager.Request; import android.database.Cursor; -import android.net.DownloadManager.Query; -import android.net.DownloadManager.Request; +import android.net.Uri; import android.os.ParcelFileDescriptor; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index 86eda71..39258ae 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -135,28 +135,6 @@ public class SQLiteDatabaseTest extends AndroidTestCase { c.close(); } - @SmallTest - public void testSetConnectionPoolSize() { - mDatabase.enableWriteAheadLogging(); - // can't set pool size to zero - try { - mDatabase.setConnectionPoolSize(0); - fail("IllegalStateException expected"); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("less than the current max value")); - } - // set pool size to a valid value - mDatabase.setConnectionPoolSize(10); - assertEquals(10, mDatabase.mConnectionPool.getMaxPoolSize()); - // can't set pool size to < the value above - try { - mDatabase.setConnectionPoolSize(1); - fail("IllegalStateException expected"); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("less than the current max value")); - } - } - /** * a transaction should be started before a standalone-update/insert/delete statement */ @@ -299,7 +277,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase { if (useWal) { // set up connection pool mDatabase.enableWriteAheadLogging(); - mDatabase.setConnectionPoolSize(i + 1); + mDatabase.mConnectionPool.setMaxPoolSize(i + 1); } else { mDatabase.disableWriteAheadLogging(); } diff --git a/core/tests/coretests/src/android/pim/RecurrenceSetTest.java b/core/tests/coretests/src/android/pim/RecurrenceSetTest.java index 64cd6c4..5d01ba0 100644 --- a/core/tests/coretests/src/android/pim/RecurrenceSetTest.java +++ b/core/tests/coretests/src/android/pim/RecurrenceSetTest.java @@ -46,7 +46,7 @@ public class RecurrenceSetTest extends TestCase { String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090822\n" + "RRULE:FREQ=YEARLY;WKST=SU"; verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null, - null, null, 1250812800000L, null, "P1D", 1); + null, null, 1250812800000L, "UTC", "P1D", 1); } // Test 2 day all-day event @@ -55,7 +55,7 @@ public class RecurrenceSetTest extends TestCase { String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090823\n" + "RRULE:FREQ=YEARLY;WKST=SU"; verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null, - null, null, 1250812800000L, null, "P2D", 1); + null, null, 1250812800000L, "UTC", "P2D", 1); } // run populateContentValues and verify the results diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk index 576765c..7206f4a 100644 --- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk @@ -19,7 +19,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) \ - ../../../coretests/src/android/net/DownloadManagerBaseTest.java + ../../../coretests/src/android/app/DownloadManagerBaseTest.java LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib LOCAL_SDK_VERSION := current diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java index f21e7ac..c0f670b 100644 --- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java @@ -15,13 +15,13 @@ */ package com.android.frameworks.downloadmanagertests; +import android.app.DownloadManager; +import android.app.DownloadManager.Query; +import android.app.DownloadManager.Request; +import android.app.DownloadManagerBaseTest; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.net.DownloadManager; -import android.net.DownloadManager.Query; -import android.net.DownloadManager.Request; -import android.net.DownloadManagerBaseTest; import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; diff --git a/data/etc/android.hardware.audio.low_latency.xml b/data/etc/android.hardware.audio.low_latency.xml new file mode 100644 index 0000000..677ec1c --- /dev/null +++ b/data/etc/android.hardware.audio.low_latency.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- This is the feature indicating low-latency audio, as specified by the + CDD. ONLY devices that meet the CDD's requirements may declare this + feature. --> +<permissions> + <feature name="android.hardware.audio.low_latency" /> +</permissions> diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml index a3c9f6d..7f87b79 100644 --- a/data/etc/handheld_core_hardware.xml +++ b/data/etc/handheld_core_hardware.xml @@ -57,4 +57,7 @@ android.hardware.sensor.proximity.xml --> <!-- GSM phones must also include android.hardware.telephony.gsm.xml --> <!-- CDMA phones must also include android.hardware.telephony.cdma.xml --> + <!-- Devices that have low-latency audio stacks suitable for apps like + VoIP may include android.hardware.audio.low_latency.xml. ONLY apps + that meet the requirements specified in the CDD may include this. --> </permissions> diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index 2aa3e84..c4421c3 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -601,9 +601,9 @@ public class RenderScript { while(mRun) { rbuf[0] = 0; int msg = mRS.nContextGetMessage(mRS.mContext, rbuf, true); - if ((msg == 0) && mRun) { + if ((msg == 0)) { // Can happen for two reasons - if (rbuf[0] > 0) { + if (rbuf[0] > 0 && mRun) { // 1: Buffer needs to be enlarged. rbuf = new int[rbuf[0] + 2]; } else { @@ -616,6 +616,7 @@ public class RenderScript { } catch(InterruptedException e) { } } + continue; } if(mRS.mMessageCallback != null) { mRS.mMessageCallback.mData = rbuf; diff --git a/include/camera/CameraHardwareInterface.h b/include/camera/CameraHardwareInterface.h index 515d879..3a77dd1 100644 --- a/include/camera/CameraHardwareInterface.h +++ b/include/camera/CameraHardwareInterface.h @@ -87,7 +87,7 @@ class CameraHardwareInterface : public virtual RefBase { public: virtual ~CameraHardwareInterface() { } - /** Set the ISurface from which the preview buffers should be dequeued */ + /** Set the ANativeWindow to which preview frames are sent */ virtual status_t setPreviewWindow(const sp<ANativeWindow>& buf) = 0; /** Return the IMemoryHeap for the raw image heap */ diff --git a/include/media/stagefright/AudioPlayer.h b/include/media/stagefright/AudioPlayer.h index 9a09586..ed2f7d7 100644 --- a/include/media/stagefright/AudioPlayer.h +++ b/include/media/stagefright/AudioPlayer.h @@ -27,6 +27,7 @@ namespace android { class MediaSource; class AudioTrack; +class AwesomePlayer; class AudioPlayer : public TimeSource { public: @@ -35,7 +36,9 @@ public: SEEK_COMPLETE }; - AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink); + AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink, + AwesomePlayer *audioObserver = NULL); + virtual ~AudioPlayer(); // Caller retains ownership of "source". @@ -91,6 +94,7 @@ private: MediaBuffer *mFirstBuffer; sp<MediaPlayerBase::AudioSink> mAudioSink; + AwesomePlayer *mObserver; static void AudioCallback(int event, void *user, void *info); void AudioCallback(int event, void *info); diff --git a/include/media/stagefright/MPEG2TSWriter.h b/include/media/stagefright/MPEG2TSWriter.h new file mode 100644 index 0000000..551ca01 --- /dev/null +++ b/include/media/stagefright/MPEG2TSWriter.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef MPEG2TS_WRITER_H_ + +#define MPEG2TS_WRITER_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/AHandlerReflector.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/MediaWriter.h> + +namespace android { + +struct MPEG2TSWriter : public MediaWriter { + MPEG2TSWriter(const char *filename); + + virtual status_t addSource(const sp<MediaSource> &source); + virtual status_t start(MetaData *param = NULL); + virtual status_t stop(); + virtual status_t pause(); + virtual bool reachedEOS(); + virtual status_t dump(int fd, const Vector<String16>& args); + + void onMessageReceived(const sp<AMessage> &msg); + +protected: + virtual ~MPEG2TSWriter(); + +private: + enum { + kWhatSourceNotify = 'noti' + }; + + struct SourceInfo; + + FILE *mFile; + sp<ALooper> mLooper; + sp<AHandlerReflector<MPEG2TSWriter> > mReflector; + + bool mStarted; + + Vector<sp<SourceInfo> > mSources; + size_t mNumSourcesDone; + + int64_t mNumTSPacketsWritten; + int64_t mNumTSPacketsBeforeMeta; + + void writeTS(); + void writeProgramAssociationTable(); + void writeProgramMap(); + void writeAccessUnit(int32_t sourceIndex, const sp<ABuffer> &buffer); + + DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSWriter); +}; + +} // namespace android + +#endif // MPEG2TS_WRITER_H_ diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index 1594e31..ab2f11d 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -95,6 +95,8 @@ enum { // Ogg files can be tagged to be automatically looping... kKeyAutoLoop = 'autL', // bool (int32_t) + + kKeyValidSamples = 'valD', // int32_t }; enum { diff --git a/include/storage/IMountService.h b/include/storage/IMountService.h index a2735a4..436fc38 100644 --- a/include/storage/IMountService.h +++ b/include/storage/IMountService.h @@ -62,7 +62,8 @@ public: virtual void finishMediaUpdate() = 0; virtual void mountObb(const String16& filename, const String16& key, const sp<IObbActionListener>& token) = 0; - virtual void unmountObb(const String16& filename, const bool force) = 0; + virtual void unmountObb(const String16& filename, const bool force, + const sp<IObbActionListener>& token) = 0; virtual bool isObbMounted(const String16& filename) = 0; virtual bool getMountedObbPath(const String16& filename, String16& path) = 0; }; diff --git a/include/ui/Input.h b/include/ui/Input.h index b587e94..21baf32 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -40,10 +40,18 @@ enum { /* * Maximum number of pointers supported per motion event. + * Smallest number of pointers is 1. */ #define MAX_POINTERS 10 /* + * Maximum pointer id value supported in a motion event. + * Smallest pointer id is 0. + * (This is limited by our use of BitSet32 to track pointer assignments.) + */ +#define MAX_POINTER_ID 31 + +/* * Declare a concrete type for the NDK's input event forward declaration. */ struct AInputEvent { diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h index 96b4fae..8d4654f 100644 --- a/include/ui/InputDispatcher.h +++ b/include/ui/InputDispatcher.h @@ -27,6 +27,7 @@ #include <utils/String8.h> #include <utils/Looper.h> #include <utils/Pool.h> +#include <utils/BitSet.h> #include <stddef.h> #include <unistd.h> @@ -89,17 +90,13 @@ struct InputTarget { * AMOTION_EVENT_ACTION_OUTSIDE to this target. */ FLAG_OUTSIDE = 0x02, - /* This flag indicates that a KeyEvent or MotionEvent is being canceled. - * In the case of a key event, it should be delivered with flag - * AKEY_EVENT_FLAG_CANCELED set. - * In the case of a motion event, it should be delivered with action - * AMOTION_EVENT_ACTION_CANCEL instead. */ - FLAG_CANCEL = 0x04, - /* This flag indicates that the target of a MotionEvent is partly or wholly * obscured by another visible window above it. The motion event should be * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */ - FLAG_WINDOW_IS_OBSCURED = 0x08, + FLAG_WINDOW_IS_OBSCURED = 0x04, + + /* This flag indicates that a motion event is being split across multiple windows. */ + FLAG_SPLIT = 0x08, }; // The input channel to be targeted. @@ -111,6 +108,13 @@ struct InputTarget { // The x and y offset to add to a MotionEvent as it is delivered. // (ignored for KeyEvents) float xOffset, yOffset; + + // The window type of the input target. + int32_t windowType; + + // The subset of pointer ids to include in motion events dispatched to this input target + // if FLAG_SPLIT is set. + BitSet32 pointerIds; }; @@ -143,7 +147,7 @@ struct InputWindow { FLAG_SHOW_WALLPAPER = 0x00100000, FLAG_TURN_SCREEN_ON = 0x00200000, FLAG_DISMISS_KEYGUARD = 0x00400000, - FLAG_IMMERSIVE = 0x00800000, + FLAG_SPLIT_TOUCH = 0x00800000, FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000, FLAG_COMPATIBLE_WINDOW = 0x20000000, FLAG_SYSTEM_ERROR = 0x40000000, @@ -276,7 +280,7 @@ public: const KeyEvent* keyEvent, uint32_t policyFlags) = 0; /* Poke user activity for an event dispatched to a window. */ - virtual void pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType) = 0; + virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0; /* Checks whether a given application pid/uid has permission to inject input events * into other applications. @@ -351,6 +355,14 @@ public: */ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0; + /* Transfers touch focus from the window associated with one channel to the + * window associated with the other channel. + * + * Returns true on success. False if the window did not actually have touch focus. + */ + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) = 0; + /* Registers or unregister input channels that may be used as targets for input events. * If monitor is true, the channel will receive a copy of all input events. * @@ -405,6 +417,9 @@ public: virtual void setFocusedApplication(const InputApplication* inputApplication); virtual void setInputDispatchMode(bool enabled, bool frozen); + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel); + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor); virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); @@ -415,6 +430,16 @@ private: T* prev; }; + struct InjectionState { + mutable int32_t refCount; + + int32_t injectorPid; + int32_t injectorUid; + int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING + bool injectionIsAsync; // set to true if injection is not waiting for the result + int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress + }; + struct EventEntry : Link<EventEntry> { enum { TYPE_SENTINEL, @@ -423,21 +448,14 @@ private: TYPE_MOTION }; - int32_t refCount; + mutable int32_t refCount; int32_t type; nsecs_t eventTime; - - int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING - bool injectionIsAsync; // set to true if injection is not waiting for the result - int32_t injectorPid; // -1 if not injected - int32_t injectorUid; // -1 if not injected + InjectionState* injectionState; bool dispatchInProgress; // initially false, set to true while dispatching - int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress - - inline bool isInjected() { return injectorPid >= 0; } - void recycle(); + inline bool isInjected() { return injectionState != NULL; } }; struct ConfigurationChangedEntry : EventEntry { @@ -463,8 +481,6 @@ private: INTERCEPT_KEY_RESULT_CONTINUE, }; InterceptKeyResult interceptKeyResult; // set based on the interception result - - void recycle(); }; struct MotionSample { @@ -521,6 +537,10 @@ private: inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; } + + inline bool isSplit() const { + return targetFlags & InputTarget::FLAG_SPLIT; + } }; // A command entry captures state and behavior for an action to be performed in the @@ -555,7 +575,6 @@ private: KeyEntry* keyEntry; sp<InputChannel> inputChannel; sp<InputApplicationHandle> inputApplicationHandle; - int32_t windowType; int32_t userActivityEventType; }; @@ -611,6 +630,7 @@ private: public: Allocator(); + InjectionState* obtainInjectionState(int32_t injectorPid, int32_t injectorUid); ConfigurationChangedEntry* obtainConfigurationChangedEntry(nsecs_t eventTime); KeyEntry* obtainKeyEntry(nsecs_t eventTime, int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, @@ -626,6 +646,7 @@ private: int32_t targetFlags, float xOffset, float yOffset); CommandEntry* obtainCommandEntry(Command command); + void releaseInjectionState(InjectionState* injectionState); void releaseEventEntry(EventEntry* entry); void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry); void releaseKeyEntry(KeyEntry* entry); @@ -633,10 +654,13 @@ private: void releaseDispatchEntry(DispatchEntry* entry); void releaseCommandEntry(CommandEntry* entry); + void recycleKeyEntry(KeyEntry* entry); + void appendMotionSample(MotionEntry* motionEntry, nsecs_t eventTime, const PointerCoords* pointerCoords); private: + Pool<InjectionState> mInjectionStatePool; Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool; Pool<KeyEntry> mKeyEntryPool; Pool<MotionEntry> mMotionEntryPool; @@ -645,6 +669,7 @@ private: Pool<CommandEntry> mCommandEntryPool; void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime); + void releaseEventEntryInjectionState(EventEntry* entry); }; /* Tracks dispatched key and motion event state so that cancelation events can be @@ -823,6 +848,7 @@ private: void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult); Condition mInjectionSyncFinishedCondition; + void incrementPendingForegroundDispatchesLocked(EventEntry* entry); void decrementPendingForegroundDispatchesLocked(EventEntry* entry); // Throttling state. @@ -858,23 +884,37 @@ private: // Dispatch state. bool mDispatchEnabled; bool mDispatchFrozen; + Vector<InputWindow> mWindows; - Vector<InputWindow*> mWallpaperWindows; + + const InputWindow* getWindowLocked(const sp<InputChannel>& inputChannel); // Focus tracking for keys, trackball, etc. - InputWindow* mFocusedWindow; + const InputWindow* mFocusedWindow; // Focus tracking for touch. - bool mTouchDown; - InputWindow* mTouchedWindow; // primary target for current down - bool mTouchedWindowIsObscured; // true if other windows may obscure the target - Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets - struct OutsideTarget { - InputWindow* window; - bool obscured; + struct TouchedWindow { + const InputWindow* window; + int32_t targetFlags; + BitSet32 pointerIds; + sp<InputChannel> channel; }; - Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets - Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets + struct TouchState { + bool down; + bool split; + Vector<TouchedWindow> windows; + + TouchState(); + ~TouchState(); + void reset(); + void copyFrom(const TouchState& other); + void addOrUpdateWindow(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds); + void removeOutsideTouchWindows(); + const InputWindow* getFirstForegroundWindow(); + }; + + TouchState mTouchState; + TouchState mTempTouchState; // Focused application. InputApplication* mFocusedApplication; @@ -899,8 +939,6 @@ private: // The input targets that were most recently identified for dispatch. bool mCurrentInputTargetsValid; // false while targets are being recomputed Vector<InputTarget> mCurrentInputTargets; - int32_t mCurrentInputWindowType; - sp<InputChannel> mCurrentInputChannel; enum InputTargetWaitCause { INPUT_TARGET_WAIT_CAUSE_NONE, @@ -915,7 +953,7 @@ private: // Finding targets for input events. void resetTargetsLocked(); - void commitTargetsLocked(const InputWindow* window); + void commitTargetsLocked(); int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, const InputApplication* application, const InputWindow* window, nsecs_t* nextWakeupTime); @@ -924,19 +962,19 @@ private: nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime); void resetANRTimeoutsLocked(); - int32_t findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry, - nsecs_t* nextWakeupTime, InputWindow** outWindow); - int32_t findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry, - nsecs_t* nextWakeupTime, InputWindow** outWindow); + int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, + nsecs_t* nextWakeupTime); + int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry, + nsecs_t* nextWakeupTime); - void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags); + void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, + BitSet32 pointerIds); void addMonitoringTargetsLocked(); - void pokeUserActivityLocked(nsecs_t eventTime, int32_t windowType, int32_t eventType); - bool checkInjectionPermission(const InputWindow* window, - int32_t injectorPid, int32_t injectorUid); + bool shouldPokeUserActivityForCurrentInputTargetsLocked(); + void pokeUserActivityLocked(nsecs_t eventTime, int32_t eventType); + bool checkInjectionPermission(const InputWindow* window, const InjectionState* injectionState); bool isWindowObscuredLocked(const InputWindow* window); bool isWindowFinishedWithPreviousInputLocked(const InputWindow* window); - void releaseTouchedWindowLocked(); String8 getApplicationWindowLabelLocked(const InputApplication* application, const InputWindow* window); @@ -955,6 +993,9 @@ private: void drainOutboundQueueLocked(Connection* connection); static int handleReceiveCallback(int receiveFd, int events, void* data); + // Splitting motion events across windows. + MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds); + // Dump state. void dumpDispatchStateLocked(String8& dump); void logDispatchStateLocked(); diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h index 903c3c4..e85735a 100644 --- a/include/ui/InputReader.h +++ b/include/ui/InputReader.h @@ -549,10 +549,6 @@ public: const int32_t* keyCodes, uint8_t* outFlags); protected: - /* Maximum pointer id value supported. - * (This is limited by our use of BitSet32 to track pointer assignments.) */ - static const uint32_t MAX_POINTER_ID = 31; - Mutex mLock; struct VirtualKey { diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h index 19c8bf0..f5dbcd9 100644 --- a/include/utils/BitSet.h +++ b/include/utils/BitSet.h @@ -38,6 +38,9 @@ struct BitSet32 { // Clears the bit set. inline void clear() { value = 0; } + // Returns the number of marked bits in the set. + inline uint32_t count() const { return __builtin_popcount(value); } + // Returns true if the bit set does not contain any marked bits. inline bool isEmpty() const { return ! value; } diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java index cd8f814..dbc9133 100644 --- a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java +++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java @@ -40,6 +40,8 @@ public class RSTestCore { private ScriptC_rslist mScript; private ArrayList<UnitTest> unitTests; + private ListIterator<UnitTest> test_iter; + private UnitTest activeTest; public void init(RenderScriptGL rs, Resources res, int width, int height) { mRS = rs; @@ -54,9 +56,13 @@ public class RSTestCore { unitTests.add(new UT_primitives(this, mRes)); unitTests.add(new UT_fp_mad(this, mRes)); /* - unitTests.add(new UnitTest("<Pass>", 1)); + unitTests.add(new UnitTest(null, "<Pass>", 1)); unitTests.add(new UnitTest()); - unitTests.add(new UnitTest("<Fail>", -1)); + unitTests.add(new UnitTest(null, "<Fail>", -1)); + + for (int i = 0; i < 20; i++) { + unitTests.add(new UnitTest(null, "<Pass>", 1)); + } */ UnitTest [] uta = new UnitTest[unitTests.size()]; @@ -71,19 +77,6 @@ public class RSTestCore { uta[i].setItem(listElem); } - /* Run the actual unit tests */ - ListIterator<UnitTest> test_iter = unitTests.listIterator(); - while (test_iter.hasNext()) { - UnitTest t = test_iter.next(); - t.start(); - /* - try { - t.join(); - } catch (InterruptedException e) { - } - */ - } - mListAllocs.copyAll(); mScript.bind_gList(mListAllocs); @@ -92,10 +85,40 @@ public class RSTestCore { mScript.set_gFont(mFont); mRS.contextBindRootScript(mScript); - mRS.finish(); + + test_iter = unitTests.listIterator(); + refreshTestResults(); /* Kick off the first test */ + } + + static int count = 0; + public void checkAndRunNextTest() { + if (activeTest != null) { + if (!activeTest.isAlive()) { + /* Properly clean up on our last test */ + try { + activeTest.join(); + } + catch (InterruptedException e) { + } + activeTest = null; + } + } + + if (activeTest == null) { + if (test_iter.hasNext()) { + activeTest = test_iter.next(); + activeTest.start(); + /* This routine will only get called once when a new test + * should start running. The message handler in UnitTest.java + * ensures this. */ + } + } + count++; } public void refreshTestResults() { + checkAndRunNextTest(); + if (mListAllocs != null && mScript != null && mRS != null) { mListAllocs.copyAll(); @@ -111,6 +134,7 @@ public class RSTestCore { mScript.set_gDY(0.0f); mLastX = x; mLastY = y; + refreshTestResults(); } public void onActionMove(int x, int y) { @@ -125,5 +149,6 @@ public class RSTestCore { mLastX = x; mLastY = y; + refreshTestResults(); } } diff --git a/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java index d98b763..5eb0d67 100644 --- a/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java +++ b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java @@ -16,6 +16,7 @@ package com.android.rs.test; import android.renderscript.RenderScript.RSMessage; +import android.util.Log; public class UnitTest extends Thread { public String name; @@ -27,11 +28,15 @@ public class UnitTest extends Thread { public static final int RS_MSG_TEST_PASSED = 100; public static final int RS_MSG_TEST_FAILED = 101; + private static int numTests = 0; + public int testID; + protected UnitTest(RSTestCore rstc, String n, int initResult) { super(); mRSTC = rstc; name = n; result = initResult; + testID = numTests++; } protected UnitTest(RSTestCore rstc, String n) { @@ -56,12 +61,20 @@ public class UnitTest extends Thread { result = -1; break; default: - break; + android.util.Log.v("RenderScript", "Unit test got unexpected message"); + return; } if (mItem != null) { mItem.result = result; - mRSTC.refreshTestResults(); + try { + mRSTC.refreshTestResults(); + } + catch (IllegalStateException e) { + /* Ignore the case where our message receiver has been + disconnected. This happens when we leave the application + before it finishes running all of the unit tests. */ + } } } }; @@ -72,6 +85,9 @@ public class UnitTest extends Thread { public void run() { /* This method needs to be implemented for each subclass */ + if (mRSTC != null) { + mRSTC.refreshTestResults(); + } } } diff --git a/libs/rs/java/tests/src/com/android/rs/test/fp_mad.rs b/libs/rs/java/tests/src/com/android/rs/test/fp_mad.rs index eb82e56..dfd77e6 100644 --- a/libs/rs/java/tests/src/com/android/rs/test/fp_mad.rs +++ b/libs/rs/java/tests/src/com/android/rs/test/fp_mad.rs @@ -170,7 +170,7 @@ void fp_mad_test(uint32_t index, int test_num) { // TODO Actually verify test result accuracy rsDebug("fp_mad_test PASSED", 0); - rsSendToClient(RS_MSG_TEST_PASSED); + rsSendToClientBlocking(RS_MSG_TEST_PASSED); } diff --git a/libs/rs/java/tests/src/com/android/rs/test/primitives.rs b/libs/rs/java/tests/src/com/android/rs/test/primitives.rs index 2ba5d52..5312bcc 100644 --- a/libs/rs/java/tests/src/com/android/rs/test/primitives.rs +++ b/libs/rs/java/tests/src/com/android/rs/test/primitives.rs @@ -42,10 +42,10 @@ void primitives_test(uint32_t index, int test_num) { failed |= test_primitive_types(index); if (failed) { - rsSendToClient(RS_MSG_TEST_FAILED); + rsSendToClientBlocking(RS_MSG_TEST_FAILED); } else { - rsSendToClient(RS_MSG_TEST_PASSED); + rsSendToClientBlocking(RS_MSG_TEST_PASSED); } } diff --git a/libs/rs/java/tests/src/com/android/rs/test/rslist.rs b/libs/rs/java/tests/src/com/android/rs/test/rslist.rs index 72d1850..a5f0f6b 100644 --- a/libs/rs/java/tests/src/com/android/rs/test/rslist.rs +++ b/libs/rs/java/tests/src/com/android/rs/test/rslist.rs @@ -56,6 +56,27 @@ int root(int launchID) { int height = rsgGetHeight(); int itemHeight = 80; + int totalItemHeight = itemHeight * allocSize; + + /* Prevent scrolling above the top of the list */ + int firstItem = height - totalItemHeight; + if (firstItem < 0) { + firstItem = 0; + } + + /* Prevent scrolling past the last line of the list */ + int lastItem = -1 * (totalItemHeight - height); + if (lastItem > 0) { + lastItem = 0; + } + + if (textPos > firstItem) { + textPos = firstItem; + } + else if (textPos < lastItem) { + textPos = lastItem; + } + int currentYPos = itemHeight + textPos; for(int i = 0; i < allocSize; i ++) { diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp index 3681bc2..a7f380f 100644 --- a/libs/rs/rsContext.cpp +++ b/libs/rs/rsContext.cpp @@ -33,6 +33,8 @@ #include <GLES2/gl2ext.h> #include <cutils/sched_policy.h> +#include <sys/syscall.h> +#include <string.h> using namespace android; using namespace android::renderscript; @@ -371,11 +373,15 @@ void * Context::helperThreadProc(void *vrsc) rsc->mWorkers.mLaunchSignals[idx].init(); rsc->mWorkers.mNativeThreadId[idx] = gettid(); - //cpu_set_t cpset[16]; - //int ret = sched_getaffinity(rsc->mWorkers.mNativeThreadId[idx], sizeof(cpset), &cpset); - //LOGE("ret = %i", ret); - -//sched_setaffinity +#if 0 + typedef struct {uint64_t bits[1024 / 64]; } cpu_set_t; + cpu_set_t cpuset; + memset(&cpuset, 0, sizeof(cpuset)); + cpuset.bits[idx / 64] |= 1ULL << (idx % 64); + int ret = syscall(241, rsc->mWorkers.mNativeThreadId[idx], + sizeof(cpuset), &cpuset); + LOGE("SETAFFINITY ret = %i %s", ret, EGLUtils::strerror(ret)); +#endif setpriority(PRIO_PROCESS, rsc->mWorkers.mNativeThreadId[idx], rsc->mThreadPriority); while(rsc->mRunning) { @@ -490,6 +496,7 @@ Context::Context(Device *dev, bool isGraphics, bool useDepth) usleep(100); } + mWorkers.mCompleteSignal.init(); mWorkers.mRunningCount = 0; mWorkers.mLaunchCount = 0; for (uint32_t ct=0; ct < mWorkers.mCount; ct++) { diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h index bce9c13..b85d2a8 100644 --- a/libs/rs/rsContext.h +++ b/libs/rs/rsContext.h @@ -174,7 +174,7 @@ public: uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;} void launchThreads(WorkerCallback_t cbk, void *data); - uint32_t getWorkerPoolSize() const {return (uint32_t)mWorkers.mRunningCount;} + uint32_t getWorkerPoolSize() const {return (uint32_t)mWorkers.mCount;} protected: Device *mDev; diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp index c3ef356..918625c 100644 --- a/libs/rs/rsProgramVertex.cpp +++ b/libs/rs/rsProgramVertex.cpp @@ -108,6 +108,11 @@ void ProgramVertex::setupGL2(Context *rsc, ProgramVertexState *state, ShaderCach rsc->checkError("ProgramVertex::setupGL2 start"); if(!isUserProgram()) { + if(mConstants[0].get() == NULL) { + LOGE("Unable to set fixed function emulation matrices because allocation is missing"); + rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing"); + return; + } float *f = static_cast<float *>(mConstants[0]->getPtr()); Matrix mvp; mvp.load(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]); diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp index f905492..e9621b9 100644 --- a/libs/rs/rsScriptC.cpp +++ b/libs/rs/rsScriptC.cpp @@ -201,7 +201,7 @@ static void wc_xy(void *usr, uint32_t idx) } //LOGE("usr idx %i, x %i,%i y %i,%i", idx, mtls->xStart, mtls->xEnd, yStart, yEnd); - + //LOGE("usr ptr in %p, out %p", mtls->ptrIn, mtls->ptrOut); for (uint32_t y = yStart; y < yEnd; y++) { uint32_t offset = mtls->dimX * y; uint8_t *xPtrOut = mtls->ptrOut + (mtls->eStrideOut * offset); @@ -296,14 +296,12 @@ void ScriptC::runForEach(Context *rsc, mtls.eStrideOut = aout->getType()->getElementSizeBytes(); } - - if ((rsc->getWorkerPoolSize() > 1) && mEnviroment.mIsThreadable && - ((mtls.dimY * mtls.dimZ * mtls.dimArray) > 1)) { + if ((rsc->getWorkerPoolSize() > 1) && mEnviroment.mIsThreadable && (mtls.dimY > 1)) { //LOGE("launch 1"); rsc->launchThreads(wc_xy, &mtls); - //LOGE("launch 2"); } else { + //LOGE("launch 3"); for (uint32_t ar = mtls.arrayStart; ar < mtls.arrayEnd; ar++) { for (uint32_t z = mtls.zStart; z < mtls.zEnd; z++) { for (uint32_t y = mtls.yStart; y < mtls.yEnd; y++) { @@ -380,11 +378,11 @@ static BCCvoid* symbolLookup(BCCvoid* pContext, const BCCchar* name) if (sym) { return sym->mPtr; } - s->mEnviroment.mIsThreadable = false; sym = ScriptCState::lookupSymbolCL(name); if (sym) { return sym->mPtr; } + s->mEnviroment.mIsThreadable = false; sym = ScriptCState::lookupSymbolGL(name); if (sym) { return sym->mPtr; diff --git a/libs/rs/rsVertexArray.cpp b/libs/rs/rsVertexArray.cpp index 075a70d..16be5ea 100644 --- a/libs/rs/rsVertexArray.cpp +++ b/libs/rs/rsVertexArray.cpp @@ -84,6 +84,10 @@ void VertexArray::clear(uint32_t n) void VertexArray::add(const Attrib &a, uint32_t stride) { + // Skip padding + if(a.name[0] == '#') { + return; + } rsAssert(mCount < RS_MAX_ATTRIBS); mAttribs[mCount].set(a); mAttribs[mCount].buffer = mActiveBuffer; @@ -94,6 +98,10 @@ void VertexArray::add(const Attrib &a, uint32_t stride) void VertexArray::add(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset, const char *name) { + // Skip padding + if(name[0] == '#') { + return; + } rsAssert(mCount < RS_MAX_ATTRIBS); mAttribs[mCount].clear(); mAttribs[mCount].type = type; @@ -129,12 +137,7 @@ void VertexArray::setupGL2(const Context *rsc, class VertexArrayState *state, Sh rsc->checkError("VertexArray::setupGL2 disabled"); for (uint32_t ct=0; ct < mCount; ct++) { - int32_t slot = 0; - - if (mAttribs[ct].name[0] == '#') { - continue; - } - + int32_t slot = -1; if (sc->isUserVertexProgram()) { slot = sc->vtxAttribSlot(ct); } else { @@ -146,14 +149,11 @@ void VertexArray::setupGL2(const Context *rsc, class VertexArrayState *state, Sh slot = 2; } else if (mAttribs[ct].name == "texture0") { slot = 3; - } else { - continue; } } if(slot < 0) { continue; } - //logAttrib(ct, slot); glEnableVertexAttribArray(slot); glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer); diff --git a/libs/storage/IMountService.cpp b/libs/storage/IMountService.cpp index 902bb27..3ad9319 100644 --- a/libs/storage/IMountService.cpp +++ b/libs/storage/IMountService.cpp @@ -429,8 +429,8 @@ public: reply.readExceptionCode(); } - void mountObb(const String16& filename, const String16& key, const sp< - IObbActionListener>& token) + void mountObb(const String16& filename, const String16& key, + const sp<IObbActionListener>& token) { Parcel data, reply; data.writeInterfaceToken(IMountService::getInterfaceDescriptor()); @@ -448,7 +448,7 @@ public: } } - void unmountObb(const String16& filename, const bool force) + void unmountObb(const String16& filename, const bool force, const sp<IObbActionListener>& token) { Parcel data, reply; data.writeInterfaceToken(IMountService::getInterfaceDescriptor()); diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp index 1cf7592..16ce24b 100644 --- a/libs/ui/InputDispatcher.cpp +++ b/libs/ui/InputDispatcher.cpp @@ -69,6 +69,65 @@ static inline const char* toString(bool value) { return value ? "true" : "false"; } +static inline int32_t getMotionEventActionPointerIndex(int32_t action) { + return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) + >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; +} + +static bool isValidKeyAction(int32_t action) { + switch (action) { + case AKEY_EVENT_ACTION_DOWN: + case AKEY_EVENT_ACTION_UP: + return true; + default: + return false; + } +} + +static bool validateKeyEvent(int32_t action) { + if (! isValidKeyAction(action)) { + LOGE("Key event has invalid action code 0x%x", action); + return false; + } + return true; +} + +static bool isValidMotionAction(int32_t action) { + 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; + default: + return false; + } +} + +static bool validateMotionEvent(int32_t action, size_t pointerCount, + const int32_t* pointerIds) { + if (! isValidMotionAction(action)) { + LOGE("Motion event has invalid action code 0x%x", action); + return false; + } + if (pointerCount < 1 || pointerCount > MAX_POINTERS) { + LOGE("Motion event has invalid pointer count %d; value must be between 1 and %d.", + pointerCount, MAX_POINTERS); + return false; + } + for (size_t i = 0; i < pointerCount; i++) { + if (pointerIds[i] < 0 || pointerIds[i] > MAX_POINTER_ID) { + LOGE("Motion event has invalid pointer id %d; value must be between 0 and %d", + pointerIds[i], MAX_POINTER_ID); + return false; + } + } + return true; +} + // --- InputWindow --- @@ -91,7 +150,7 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic mPolicy(policy), mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX), mDispatchEnabled(true), mDispatchFrozen(false), - mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL), + mFocusedWindow(NULL), mFocusedApplication(NULL), mCurrentInputTargetsValid(false), mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) { @@ -414,9 +473,10 @@ void InputDispatcher::releasePendingEventLocked() { } void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) { - if (entry->injectionResult == INPUT_EVENT_INJECTION_PENDING) { + InjectionState* injectionState = entry->injectionState; + if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) { #if DEBUG_DISPATCH_CYCLE - LOGD("Inbound event was dropped. Setting injection result to failed."); + LOGD("Injected inbound event was dropped."); #endif setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED); } @@ -424,10 +484,11 @@ void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) { } bool InputDispatcher::isEventFromReliableSourceLocked(EventEntry* entry) { - return ! entry->isInjected() - || entry->injectorUid == 0 + InjectionState* injectionState = entry->injectionState; + return ! injectionState + || injectionState->injectorUid == 0 || mPolicy->checkInjectEventsPermissionNonReentrant( - entry->injectorPid, entry->injectorUid); + injectionState->injectorPid, injectionState->injectorUid); } void InputDispatcher::resetKeyRepeatLocked() { @@ -444,7 +505,7 @@ InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked( // Reuse the repeated key entry if it is otherwise unreferenced. uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK; if (entry->refCount == 1) { - entry->recycle(); + mAllocator.recycleKeyEntry(entry); entry->eventTime = currentTime; entry->policyFlags = policyFlags; entry->repeatCount += 1; @@ -496,8 +557,7 @@ bool InputDispatcher::dispatchKeyLocked( if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { bool trusted; if (! dropEvent && mFocusedWindow) { - trusted = checkInjectionPermission(mFocusedWindow, - entry->injectorPid, entry->injectorUid); + trusted = checkInjectionPermission(mFocusedWindow, entry->injectionState); } else { trusted = isEventFromReliableSourceLocked(entry); } @@ -559,9 +619,8 @@ bool InputDispatcher::dispatchKeyLocked( // Identify targets. if (! mCurrentInputTargetsValid) { - InputWindow* window = NULL; - int32_t injectionResult = findFocusedWindowLocked(currentTime, - entry, nextWakeupTime, & window); + int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { return false; } @@ -572,14 +631,16 @@ bool InputDispatcher::dispatchKeyLocked( } addMonitoringTargetsLocked(); - commitTargetsLocked(window); + commitTargetsLocked(); } // Dispatch the key. dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); // Poke user activity. - pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, POWER_MANAGER_BUTTON_EVENT); + if (shouldPokeUserActivityForCurrentInputTargetsLocked()) { + pokeUserActivityLocked(entry->eventTime, POWER_MANAGER_BUTTON_EVENT); + } return true; } @@ -616,16 +677,15 @@ bool InputDispatcher::dispatchMotionLocked( // Identify targets. if (! mCurrentInputTargetsValid) { - InputWindow* window = NULL; int32_t injectionResult; if (isPointerEvent) { // Pointer event. (eg. touchscreen) - injectionResult = findTouchedWindowLocked(currentTime, - entry, nextWakeupTime, & window); + injectionResult = findTouchedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); } else { // Non touch event. (eg. trackball) - injectionResult = findFocusedWindowLocked(currentTime, - entry, nextWakeupTime, & window); + injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); } if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { return false; @@ -637,34 +697,36 @@ bool InputDispatcher::dispatchMotionLocked( } addMonitoringTargetsLocked(); - commitTargetsLocked(window); + commitTargetsLocked(); } // Dispatch the motion. dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); // Poke user activity. - int32_t eventType; - if (isPointerEvent) { - switch (entry->action) { - case AMOTION_EVENT_ACTION_DOWN: - eventType = POWER_MANAGER_TOUCH_EVENT; - break; - case AMOTION_EVENT_ACTION_UP: - eventType = POWER_MANAGER_TOUCH_UP_EVENT; - break; - default: - if (entry->eventTime - entry->downTime >= EVENT_IGNORE_DURATION) { + if (shouldPokeUserActivityForCurrentInputTargetsLocked()) { + int32_t eventType; + if (isPointerEvent) { + switch (entry->action) { + case AMOTION_EVENT_ACTION_DOWN: eventType = POWER_MANAGER_TOUCH_EVENT; - } else { - eventType = POWER_MANAGER_LONG_TOUCH_EVENT; + break; + case AMOTION_EVENT_ACTION_UP: + eventType = POWER_MANAGER_TOUCH_UP_EVENT; + break; + default: + if (entry->eventTime - entry->downTime >= EVENT_IGNORE_DURATION) { + eventType = POWER_MANAGER_TOUCH_EVENT; + } else { + eventType = POWER_MANAGER_LONG_TOUCH_EVENT; + } + break; } - break; + } else { + eventType = POWER_MANAGER_BUTTON_EVENT; } - } else { - eventType = POWER_MANAGER_BUTTON_EVENT; + pokeUserActivityLocked(entry->eventTime, eventType); } - pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, eventType); return true; } @@ -735,13 +797,10 @@ void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTi void InputDispatcher::resetTargetsLocked() { mCurrentInputTargetsValid = false; mCurrentInputTargets.clear(); - mCurrentInputChannel.clear(); mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; } -void InputDispatcher::commitTargetsLocked(const InputWindow* window) { - mCurrentInputWindowType = window->layoutParamsType; - mCurrentInputChannel = window->inputChannel; +void InputDispatcher::commitTargetsLocked() { mCurrentInputTargetsValid = true; } @@ -803,8 +862,8 @@ void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout // Give up. mInputTargetWaitTimeoutExpired = true; - // Release the touch target. - releaseTouchedWindowLocked(); + // Release the touch targets. + mTouchState.reset(); // Input state will not be realistic. Mark it out of sync. if (inputChannel.get()) { @@ -834,9 +893,8 @@ void InputDispatcher::resetANRTimeoutsLocked() { mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; } -int32_t InputDispatcher::findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry, - nsecs_t* nextWakeupTime, InputWindow** outWindow) { - *outWindow = NULL; +int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, + const EventEntry* entry, nsecs_t* nextWakeupTime) { mCurrentInputTargets.clear(); int32_t injectionResult; @@ -861,7 +919,7 @@ int32_t InputDispatcher::findFocusedWindowLocked(nsecs_t currentTime, const Even } // Check permissions. - if (! checkInjectionPermission(mFocusedWindow, entry->injectorPid, entry->injectorUid)) { + if (! checkInjectionPermission(mFocusedWindow, entry->injectionState)) { injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; goto Failed; } @@ -888,8 +946,7 @@ int32_t InputDispatcher::findFocusedWindowLocked(nsecs_t currentTime, const Even // Success! Output targets. injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; - *outWindow = mFocusedWindow; - addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND); + addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0)); // Done. Failed: @@ -905,15 +962,14 @@ Unresponsive: return injectionResult; } -int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry, - nsecs_t* nextWakeupTime, InputWindow** outWindow) { +int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, + const MotionEntry* entry, nsecs_t* nextWakeupTime) { enum InjectionPermission { INJECTION_PERMISSION_UNKNOWN, INJECTION_PERMISSION_GRANTED, INJECTION_PERMISSION_DENIED }; - *outWindow = NULL; mCurrentInputTargets.clear(); nsecs_t startTime = now(); @@ -945,25 +1001,33 @@ int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const Moti bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE; int32_t action = entry->action; + int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK; // Update the touch state as needed based on the properties of the touch event. - int32_t injectionResult; - InjectionPermission injectionPermission; - if (action == AMOTION_EVENT_ACTION_DOWN) { - /* Case 1: ACTION_DOWN */ + int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING; + InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN; + if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + mTempTouchState.reset(); + mTempTouchState.down = true; + } else { + mTempTouchState.copyFrom(mTouchState); + } - InputWindow* newTouchedWindow = NULL; - mTempTouchedOutsideTargets.clear(); + bool isSplit = mTempTouchState.split && mTempTouchState.down; + if (maskedAction == AMOTION_EVENT_ACTION_DOWN + || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { + /* Case 1: New splittable pointer going down. */ - int32_t x = int32_t(entry->firstSample.pointerCoords[0].x); - int32_t y = int32_t(entry->firstSample.pointerCoords[0].y); - InputWindow* topErrorWindow = NULL; - bool obscured = false; + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex].x); + int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex].y); + const InputWindow* newTouchedWindow = NULL; + const InputWindow* topErrorWindow = NULL; // Traverse windows from front to back to find touched window and outside targets. size_t numWindows = mWindows.size(); for (size_t i = 0; i < numWindows; i++) { - InputWindow* window = & mWindows.editItemAt(i); + const InputWindow* window = & mWindows.editItemAt(i); int32_t flags = window->layoutParamsFlags; if (flags & InputWindow::FLAG_SYSTEM_ERROR) { @@ -979,17 +1043,15 @@ int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const Moti if (isTouchModal || window->touchableAreaContainsPoint(x, y)) { if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) { newTouchedWindow = window; - obscured = isWindowObscuredLocked(window); } break; // found touched window, exit window loop } } - if (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH) { - OutsideTarget outsideTarget; - outsideTarget.window = window; - outsideTarget.obscured = isWindowObscuredLocked(window); - mTempTouchedOutsideTargets.push(outsideTarget); + if (maskedAction == AMOTION_EVENT_ACTION_DOWN + && (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH)) { + mTempTouchState.addOrUpdateWindow(window, + InputTarget::FLAG_OUTSIDE, BitSet32(0)); } } } @@ -1007,6 +1069,22 @@ int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const Moti goto Unresponsive; } + // Figure out whether splitting will be allowed for this window. + if (newTouchedWindow + && (newTouchedWindow->layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH)) { + // New window supports splitting. + isSplit = true; + } else if (isSplit) { + // New window does not support splitting but we have already split events. + // Assign the pointer to the first foreground window we find. + // (May be NULL which is why we put this code block before the next check.) + newTouchedWindow = mTempTouchState.getFirstForegroundWindow(); + } + int32_t targetFlags = InputTarget::FLAG_FOREGROUND; + if (isSplit) { + targetFlags |= InputTarget::FLAG_SPLIT; + } + // If we did not find a touched window then fail. if (! newTouchedWindow) { if (mFocusedApplication) { @@ -1017,140 +1095,127 @@ int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const Moti #endif injectionResult = handleTargetsNotReadyLocked(currentTime, entry, mFocusedApplication, NULL, nextWakeupTime); - injectionPermission = INJECTION_PERMISSION_UNKNOWN; goto Unresponsive; } LOGI("Dropping event because there is no touched window or focused application."); injectionResult = INPUT_EVENT_INJECTION_FAILED; - injectionPermission = INJECTION_PERMISSION_UNKNOWN; goto Failed; } - // Check permissions. - if (! checkInjectionPermission(newTouchedWindow, entry->injectorPid, entry->injectorUid)) { - injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; - injectionPermission = INJECTION_PERMISSION_DENIED; - goto Failed; - } - - // If the touched window is paused then keep waiting. - if (newTouchedWindow->paused) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Waiting because touched window is paused."); -#endif - injectionResult = handleTargetsNotReadyLocked(currentTime, entry, - NULL, newTouchedWindow, nextWakeupTime); - injectionPermission = INJECTION_PERMISSION_GRANTED; - goto Unresponsive; - } - - // If the touched window is still working on previous events then keep waiting. - if (! isWindowFinishedWithPreviousInputLocked(newTouchedWindow)) { -#if DEBUG_FOCUS - LOGD("Waiting because touched window still processing previous input."); -#endif - injectionResult = handleTargetsNotReadyLocked(currentTime, entry, - NULL, newTouchedWindow, nextWakeupTime); - injectionPermission = INJECTION_PERMISSION_GRANTED; - goto Unresponsive; - } - - // Success! Update the touch dispatch state for real. - releaseTouchedWindowLocked(); - - mTouchedWindow = newTouchedWindow; - mTouchedWindowIsObscured = obscured; - - if (newTouchedWindow->hasWallpaper) { - mTouchedWallpaperWindows.appendVector(mWallpaperWindows); + // Update the temporary touch state. + BitSet32 pointerIds; + if (isSplit) { + uint32_t pointerId = entry->pointerIds[pointerIndex]; + pointerIds.markBit(pointerId); } + mTempTouchState.addOrUpdateWindow(newTouchedWindow, targetFlags, pointerIds); } else { - /* Case 2: Everything but ACTION_DOWN */ - - // Check permissions. - if (! checkInjectionPermission(mTouchedWindow, entry->injectorPid, entry->injectorUid)) { - injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; - injectionPermission = INJECTION_PERMISSION_DENIED; - goto Failed; - } + /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ // If the pointer is not currently down, then ignore the event. - if (! mTouchDown) { + if (! mTempTouchState.down) { LOGI("Dropping event because the pointer is not down."); injectionResult = INPUT_EVENT_INJECTION_FAILED; - injectionPermission = INJECTION_PERMISSION_GRANTED; goto Failed; } + } - // If there is no currently touched window then fail. - if (! mTouchedWindow) { + // Check permission to inject into all touched foreground windows and ensure there + // is at least one touched foreground window. + { + bool haveForegroundWindow = false; + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTempTouchState.windows[i]; + if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) { + haveForegroundWindow = true; + if (! checkInjectionPermission(touchedWindow.window, entry->injectionState)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + injectionPermission = INJECTION_PERMISSION_DENIED; + goto Failed; + } + } + } + if (! haveForegroundWindow) { #if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Dropping event because there is no touched window to receive it."); + LOGD("Dropping event because there is no touched foreground window to receive it."); #endif injectionResult = INPUT_EVENT_INJECTION_FAILED; - injectionPermission = INJECTION_PERMISSION_GRANTED; goto Failed; } - // If the touched window is paused then keep waiting. - if (mTouchedWindow->paused) { + // Permission granted to injection into all touched foreground windows. + injectionPermission = INJECTION_PERMISSION_GRANTED; + } + + // Ensure all touched foreground windows are ready for new input. + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTempTouchState.windows[i]; + if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) { + // If the touched window is paused then keep waiting. + if (touchedWindow.window->paused) { #if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Waiting because touched window is paused."); + LOGD("Waiting because touched window is paused."); #endif - injectionResult = handleTargetsNotReadyLocked(currentTime, entry, - NULL, mTouchedWindow, nextWakeupTime); - injectionPermission = INJECTION_PERMISSION_GRANTED; - goto Unresponsive; - } + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.window, nextWakeupTime); + goto Unresponsive; + } - // If the touched window is still working on previous events then keep waiting. - if (! isWindowFinishedWithPreviousInputLocked(mTouchedWindow)) { + // If the touched window is still working on previous events then keep waiting. + if (! isWindowFinishedWithPreviousInputLocked(touchedWindow.window)) { #if DEBUG_FOCUS - LOGD("Waiting because touched window still processing previous input."); + LOGD("Waiting because touched window still processing previous input."); #endif - injectionResult = handleTargetsNotReadyLocked(currentTime, entry, - NULL, mTouchedWindow, nextWakeupTime); - injectionPermission = INJECTION_PERMISSION_GRANTED; - goto Unresponsive; + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.window, nextWakeupTime); + goto Unresponsive; + } } } - // Success! Output targets. - injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; - injectionPermission = INJECTION_PERMISSION_GRANTED; - - { - size_t numWallpaperWindows = mTouchedWallpaperWindows.size(); - for (size_t i = 0; i < numWallpaperWindows; i++) { - addWindowTargetLocked(mTouchedWallpaperWindows[i], - InputTarget::FLAG_WINDOW_IS_OBSCURED); - } - - size_t numOutsideTargets = mTempTouchedOutsideTargets.size(); - for (size_t i = 0; i < numOutsideTargets; i++) { - const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i]; - int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE; - if (outsideTarget.obscured) { - outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + // If this is the first pointer going down and the touched window has a wallpaper + // then also add the touched wallpaper windows so they are locked in for the duration + // of the touch gesture. + if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + const InputWindow* foregroundWindow = mTempTouchState.getFirstForegroundWindow(); + if (foregroundWindow->hasWallpaper) { + for (size_t i = 0; i < mWindows.size(); i++) { + const InputWindow* window = & mWindows[i]; + if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) { + mTempTouchState.addOrUpdateWindow(window, 0, BitSet32(0)); + } } - addWindowTargetLocked(outsideTarget.window, outsideTargetFlags); } - mTempTouchedOutsideTargets.clear(); + } - int32_t targetFlags = InputTarget::FLAG_FOREGROUND; - if (mTouchedWindowIsObscured) { - targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + // If a touched window has been obscured at any point during the touch gesture, set + // the appropriate flag so we remember it for the entire gesture. + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i); + if ((touchedWindow.targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) == 0) { + if (isWindowObscuredLocked(touchedWindow.window)) { + touchedWindow.targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } } - addWindowTargetLocked(mTouchedWindow, targetFlags); - *outWindow = mTouchedWindow; } + // Success! Output targets. + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i); + addWindowTargetLocked(touchedWindow.window, touchedWindow.targetFlags, + touchedWindow.pointerIds); + } + + // Drop the outside touch window since we will not care about them in the next iteration. + mTempTouchState.removeOutsideTouchWindows(); + Failed: // Check injection permission once and for all. if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) { - if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow, - entry->injectorPid, entry->injectorUid)) { + if (checkInjectionPermission(NULL, entry->injectionState)) { injectionPermission = INJECTION_PERMISSION_GRANTED; } else { injectionPermission = INJECTION_PERMISSION_DENIED; @@ -1159,25 +1224,41 @@ Failed: // Update final pieces of touch state if the injector had permission. if (injectionPermission == INJECTION_PERMISSION_GRANTED) { - if (action == AMOTION_EVENT_ACTION_DOWN) { - if (mTouchDown) { - // This is weird. We got a down but we thought it was already down! + if (maskedAction == AMOTION_EVENT_ACTION_UP + || maskedAction == AMOTION_EVENT_ACTION_CANCEL) { + // All pointers up or canceled. + mTempTouchState.reset(); + } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + // First pointer went down. + if (mTouchState.down) { LOGW("Pointer down received while already down."); - } else { - mTouchDown = true; } - - if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { - // Since we failed to identify a target for this touch down, we may still - // be holding on to an earlier target from a previous touch down. Release it. - releaseTouchedWindowLocked(); + } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + // One pointer went up. + if (isSplit) { + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + uint32_t pointerId = entry->pointerIds[pointerIndex]; + + for (size_t i = 0; i < mTempTouchState.windows.size(); ) { + TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i); + if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) { + touchedWindow.pointerIds.clearBit(pointerId); + if (touchedWindow.pointerIds.isEmpty()) { + mTempTouchState.windows.removeAt(i); + continue; + } + } + i += 1; + } } - } else if (action == AMOTION_EVENT_ACTION_UP) { - mTouchDown = false; - releaseTouchedWindowLocked(); } + + // Save changes to touch state. + mTouchState.copyFrom(mTempTouchState); } else { - LOGW("Not updating touch focus because injection was denied."); +#if DEBUG_FOCUS + LOGD("Not updating touch focus because injection was denied."); +#endif } Unresponsive: @@ -1185,20 +1266,15 @@ Unresponsive: updateDispatchStatisticsLocked(currentTime, entry, injectionResult, timeSpentWaitingForApplication); #if DEBUG_FOCUS - LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d," - "timeSpendWaitingForApplication=%0.1fms", + LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, " + "timeSpentWaitingForApplication=%0.1fms", injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0); #endif return injectionResult; } -void InputDispatcher::releaseTouchedWindowLocked() { - mTouchedWindow = NULL; - mTouchedWindowIsObscured = false; - mTouchedWallpaperWindows.clear(); -} - -void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags) { +void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, + BitSet32 pointerIds) { mCurrentInputTargets.push(); InputTarget& target = mCurrentInputTargets.editTop(); @@ -1206,6 +1282,8 @@ void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t t target.flags = targetFlags; target.xOffset = - window->frameLeft; target.yOffset = - window->frameTop; + target.windowType = window->layoutParamsType; + target.pointerIds = pointerIds; } void InputDispatcher::addMonitoringTargetsLocked() { @@ -1217,22 +1295,27 @@ void InputDispatcher::addMonitoringTargetsLocked() { target.flags = 0; target.xOffset = 0; target.yOffset = 0; + target.windowType = InputWindow::TYPE_SYSTEM_OVERLAY; } } bool InputDispatcher::checkInjectionPermission(const InputWindow* window, - int32_t injectorPid, int32_t injectorUid) { - if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) { - bool result = mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid); + 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", - injectorPid, injectorUid, window->inputChannel->getName().string(), + injectionState->injectorPid, injectionState->injectorUid, + window->inputChannel->getName().string(), window->ownerUid); } else { LOGW("Permission denied: injecting event from pid %d uid %d", - injectorPid, injectorUid); + injectionState->injectorPid, injectionState->injectorUid); } return false; } @@ -1282,12 +1365,19 @@ String8 InputDispatcher::getApplicationWindowLabelLocked(const InputApplication* } } -void InputDispatcher::pokeUserActivityLocked(nsecs_t eventTime, - int32_t windowType, int32_t eventType) { +bool InputDispatcher::shouldPokeUserActivityForCurrentInputTargetsLocked() { + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + if (mCurrentInputTargets[i].windowType == InputWindow::TYPE_KEYGUARD) { + return false; + } + } + return true; +} + +void InputDispatcher::pokeUserActivityLocked(nsecs_t eventTime, int32_t eventType) { CommandEntry* commandEntry = postCommandLocked( & InputDispatcher::doPokeUserActivityLockedInterruptible); commandEntry->eventTime = eventTime; - commandEntry->windowType = windowType; commandEntry->userActivityEventType = eventType; } @@ -1296,12 +1386,19 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, bool resumeWithAppendedMotionSample) { #if DEBUG_DISPATCH_CYCLE LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, " - "xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s", + "xOffset=%f, yOffset=%f, " + "windowType=%d, pointerIds=0x%x, " + "resumeWithAppendedMotionSample=%s", connection->getInputChannelName(), inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset, + inputTarget->windowType, inputTarget->pointerIds.value, toString(resumeWithAppendedMotionSample)); #endif + // Make sure we are never called for streaming when splitting across multiple windows. + bool isSplit = inputTarget->flags & InputTarget::FLAG_SPLIT; + assert(! (resumeWithAppendedMotionSample && isSplit)); + // 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) { @@ -1310,6 +1407,23 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, return; } + // Split a motion event if needed. + if (isSplit) { + assert(eventEntry->type == EventEntry::TYPE_MOTION); + + MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry); + if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) { + MotionEntry* splitMotionEntry = splitMotionEvent( + originalMotionEntry, inputTarget->pointerIds); +#if DEBUG_FOCUS + LOGD("channel '%s' ~ Split motion event.", + connection->getInputChannelName()); + logOutboundMotionDetailsLocked(" ", splitMotionEntry); +#endif + eventEntry = splitMotionEntry; + } + } + // Resume the dispatch cycle with a freshly appended motion sample. // First we check that the last dispatch entry in the outbound queue is for the same // motion event to which we appended the motion sample. If we find such a dispatch @@ -1351,7 +1465,8 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, // The dispatch entry is in progress and is still potentially open for streaming. // Try to stream the new motion sample. This might fail if the consumer has already // consumed the motion event (or if the channel is broken). - MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample; + MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry); + MotionSample* appendedMotionSample = motionEntry->lastSample; status_t status = connection->inputPublisher.appendMotionSample( appendedMotionSample->eventTime, appendedMotionSample->pointerCoords); if (status == OK) { @@ -1426,7 +1541,7 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset); if (dispatchEntry->hasForegroundTarget()) { - eventEntry->pendingForegroundDispatches += 1; + incrementPendingForegroundDispatchesLocked(eventEntry); } // Handle the case where we could not stream a new motion sample because the consumer has @@ -1470,8 +1585,8 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, dispatchEntry->inProgress = true; // Update the connection's input state. - InputState::Consistency consistency = connection->inputState.trackEvent( - dispatchEntry->eventEntry); + EventEntry* eventEntry = dispatchEntry->eventEntry; + InputState::Consistency consistency = connection->inputState.trackEvent(eventEntry); #if FILTER_INPUT_EVENTS // Filter out inconsistent sequences of input events. @@ -1497,16 +1612,13 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, // Publish the event. status_t status; - switch (dispatchEntry->eventEntry->type) { + switch (eventEntry->type) { case EventEntry::TYPE_KEY: { - KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); + KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry); // Apply target flags. int32_t action = keyEntry->action; int32_t flags = keyEntry->flags; - if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { - flags |= AKEY_EVENT_FLAG_CANCELED; - } // Publish the key event. status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source, @@ -1524,7 +1636,7 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, } case EventEntry::TYPE_MOTION: { - MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry); + MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry); // Apply target flags. int32_t action = motionEntry->action; @@ -1532,9 +1644,6 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) { action = AMOTION_EVENT_ACTION_OUTSIDE; } - if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { - action = AMOTION_EVENT_ACTION_CANCEL; - } if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) { flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; } @@ -1617,7 +1726,7 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, } // Record information about the newly started dispatch cycle. - connection->lastEventTime = dispatchEntry->eventEntry->eventTime; + connection->lastEventTime = eventEntry->eventTime; connection->lastDispatchTime = currentTime; // Notify other system components. @@ -1773,6 +1882,86 @@ int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data } // release lock } +InputDispatcher::MotionEntry* +InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) { + assert(pointerIds.value != 0); + + uint32_t splitPointerIndexMap[MAX_POINTERS]; + int32_t splitPointerIds[MAX_POINTERS]; + PointerCoords splitPointerCoords[MAX_POINTERS]; + + uint32_t originalPointerCount = originalMotionEntry->pointerCount; + uint32_t splitPointerCount = 0; + + for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount; + originalPointerIndex++) { + int32_t pointerId = uint32_t(originalMotionEntry->pointerIds[originalPointerIndex]); + if (pointerIds.hasBit(pointerId)) { + splitPointerIndexMap[splitPointerCount] = originalPointerIndex; + splitPointerIds[splitPointerCount] = pointerId; + splitPointerCoords[splitPointerCount] = + originalMotionEntry->firstSample.pointerCoords[originalPointerIndex]; + splitPointerCount += 1; + } + } + assert(splitPointerCount == pointerIds.count()); + + int32_t action = originalMotionEntry->action; + int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK; + if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN + || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + int32_t originalPointerIndex = getMotionEventActionPointerIndex(action); + int32_t pointerId = originalMotionEntry->pointerIds[originalPointerIndex]; + if (pointerIds.hasBit(pointerId)) { + if (pointerIds.count() == 1) { + // The first/last pointer went down/up. + action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN + ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; + } else { + // A secondary pointer went down/up. + uint32_t splitPointerIndex = 0; + while (pointerId != splitPointerIds[splitPointerIndex]) { + splitPointerIndex += 1; + } + action = maskedAction | (splitPointerIndex + << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + } else { + // An unrelated pointer changed. + action = AMOTION_EVENT_ACTION_MOVE; + } + } + + MotionEntry* splitMotionEntry = mAllocator.obtainMotionEntry( + originalMotionEntry->eventTime, + originalMotionEntry->deviceId, + originalMotionEntry->source, + originalMotionEntry->policyFlags, + action, + originalMotionEntry->flags, + originalMotionEntry->metaState, + originalMotionEntry->edgeFlags, + originalMotionEntry->xPrecision, + originalMotionEntry->yPrecision, + originalMotionEntry->downTime, + splitPointerCount, splitPointerIds, splitPointerCoords); + + for (MotionSample* originalMotionSample = originalMotionEntry->firstSample.next; + originalMotionSample != NULL; originalMotionSample = originalMotionSample->next) { + for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount; + splitPointerIndex++) { + uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex]; + splitPointerCoords[splitPointerIndex] = + originalMotionSample->pointerCoords[originalPointerIndex]; + } + + mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime, + splitPointerCoords); + } + + return splitMotionEntry; +} + void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) { #if DEBUG_INBOUND_EVENT_DETAILS LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime); @@ -1800,6 +1989,9 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t sou eventTime, deviceId, source, policyFlags, action, flags, keyCode, scanCode, metaState, downTime); #endif + if (! validateKeyEvent(action)) { + return; + } bool needWake; { // acquire lock @@ -1839,6 +2031,9 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t pointerCoords[i].orientation); } #endif + if (! validateMotionEvent(action, pointerCount, pointerIds)) { + return; + } bool needWake; { // acquire lock @@ -1913,8 +2108,10 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; if (! dispatchEntry->inProgress - || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) { - // No motion event is being dispatched. + || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION + || dispatchEntry->isSplit()) { + // No motion event is being dispatched, or it is being split across + // windows in which case we cannot stream. continue; } @@ -1972,24 +2169,24 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); - EventEntry* injectedEntry; + InjectionState* injectionState; bool needWake; { // acquire lock AutoMutex _l(mLock); - injectedEntry = createEntryFromInjectedInputEventLocked(event); + EventEntry* injectedEntry = createEntryFromInjectedInputEventLocked(event); if (! injectedEntry) { return INPUT_EVENT_INJECTION_FAILED; } - injectedEntry->refCount += 1; - injectedEntry->injectorPid = injectorPid; - injectedEntry->injectorUid = injectorUid; - + injectionState = mAllocator.obtainInjectionState(injectorPid, injectorUid); if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { - injectedEntry->injectionIsAsync = true; + injectionState->injectionIsAsync = true; } + injectionState->refCount += 1; + injectedEntry->injectionState = injectionState; + needWake = enqueueInboundEventLocked(injectedEntry); } // release lock @@ -2005,7 +2202,7 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; } else { for (;;) { - injectionResult = injectedEntry->injectionResult; + injectionResult = injectionState->injectionResult; if (injectionResult != INPUT_EVENT_INJECTION_PENDING) { break; } @@ -2025,10 +2222,10 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) { - while (injectedEntry->pendingForegroundDispatches != 0) { + while (injectionState->pendingForegroundDispatches != 0) { #if DEBUG_INJECTION LOGD("injectInputEvent - Waiting for %d pending foreground dispatches.", - injectedEntry->pendingForegroundDispatches); + injectionState->pendingForegroundDispatches); #endif nsecs_t remainingTimeout = endTime - now(); if (remainingTimeout <= 0) { @@ -2045,7 +2242,7 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, } } - mAllocator.releaseEventEntry(injectedEntry); + mAllocator.releaseInjectionState(injectionState); } // release lock #if DEBUG_INJECTION @@ -2058,14 +2255,15 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, } void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) { - if (entry->isInjected()) { + InjectionState* injectionState = entry->injectionState; + if (injectionState) { #if DEBUG_INJECTION LOGD("Setting input event injection result to %d. " "injectorPid=%d, injectorUid=%d", - injectionResult, entry->injectorPid, entry->injectorUid); + injectionResult, injectionState->injectorPid, injectionState->injectorUid); #endif - if (entry->injectionIsAsync) { + if (injectionState->injectionIsAsync) { // Log the outcome since the injector did not wait for the injection result. switch (injectionResult) { case INPUT_EVENT_INJECTION_SUCCEEDED: @@ -2083,41 +2281,26 @@ void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t inject } } - entry->injectionResult = injectionResult; + injectionState->injectionResult = injectionResult; mInjectionResultAvailableCondition.broadcast(); } } -void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) { - entry->pendingForegroundDispatches -= 1; - - if (entry->isInjected() && entry->pendingForegroundDispatches == 0) { - mInjectionSyncFinishedCondition.broadcast(); +void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) { + InjectionState* injectionState = entry->injectionState; + if (injectionState) { + injectionState->pendingForegroundDispatches += 1; } } -static bool isValidKeyAction(int32_t action) { - switch (action) { - case AKEY_EVENT_ACTION_DOWN: - case AKEY_EVENT_ACTION_UP: - return true; - default: - return false; - } -} +void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) { + InjectionState* injectionState = entry->injectionState; + if (injectionState) { + injectionState->pendingForegroundDispatches -= 1; -static bool isValidMotionAction(int32_t action) { - 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; - default: - return false; + if (injectionState->pendingForegroundDispatches == 0) { + mInjectionSyncFinishedCondition.broadcast(); + } } } @@ -2126,9 +2309,7 @@ InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventL switch (event->getType()) { case AINPUT_EVENT_TYPE_KEY: { const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event); - if (! isValidKeyAction(keyEvent->getAction())) { - LOGE("Dropping injected key event since it has invalid action code 0x%x", - keyEvent->getAction()); + if (! validateKeyEvent(keyEvent->getAction())) { return NULL; } @@ -2144,16 +2325,10 @@ InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventL case AINPUT_EVENT_TYPE_MOTION: { const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event); - if (! isValidMotionAction(motionEvent->getAction())) { - LOGE("Dropping injected motion event since it has invalid action code 0x%x.", - motionEvent->getAction()); + if (! validateMotionEvent(motionEvent->getAction(), + motionEvent->getPointerCount(), motionEvent->getPointerIds())) { return NULL; } - if (motionEvent->getPointerCount() == 0 - || motionEvent->getPointerCount() > MAX_POINTERS) { - LOGE("Dropping injected motion event since it has an invalid pointer count %d.", - motionEvent->getPointerCount()); - } uint32_t policyFlags = POLICY_FLAG_INJECTED; @@ -2182,6 +2357,16 @@ InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventL } } +const InputWindow* InputDispatcher::getWindowLocked(const sp<InputChannel>& inputChannel) { + for (size_t i = 0; i < mWindows.size(); i++) { + const InputWindow* window = & mWindows[i]; + if (window->inputChannel == inputChannel) { + return window; + } + } + return NULL; +} + void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { #if DEBUG_FOCUS LOGD("setInputWindows"); @@ -2189,22 +2374,8 @@ void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { { // acquire lock AutoMutex _l(mLock); - // Clear old window pointers but remember their associated channels. + // Clear old window pointers. mFocusedWindow = NULL; - - sp<InputChannel> touchedWindowChannel; - if (mTouchedWindow) { - touchedWindowChannel = mTouchedWindow->inputChannel; - mTouchedWindow = NULL; - } - size_t numTouchedWallpapers = mTouchedWallpaperWindows.size(); - if (numTouchedWallpapers != 0) { - for (size_t i = 0; i < numTouchedWallpapers; i++) { - mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel); - } - mTouchedWallpaperWindows.clear(); - } - mWallpaperWindows.clear(); mWindows.clear(); // Loop over new windows and rebuild the necessary window pointers for @@ -2213,26 +2384,23 @@ void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { size_t numWindows = mWindows.size(); for (size_t i = 0; i < numWindows; i++) { - InputWindow* window = & mWindows.editItemAt(i); + const InputWindow* window = & mWindows.itemAt(i); if (window->hasFocus) { mFocusedWindow = window; + break; } + } - if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) { - mWallpaperWindows.push(window); - - for (size_t j = 0; j < numTouchedWallpapers; j++) { - if (window->inputChannel == mTempTouchedWallpaperChannels[i]) { - mTouchedWallpaperWindows.push(window); - } - } - } - - if (window->inputChannel == touchedWindowChannel) { - mTouchedWindow = window; + for (size_t i = 0; i < mTouchState.windows.size(); ) { + TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i); + const InputWindow* window = getWindowLocked(touchedWindow.channel); + if (window) { + touchedWindow.window = window; + i += 1; + } else { + mTouchState.windows.removeAt(i); } } - mTempTouchedWallpaperChannels.clear(); #if DEBUG_FOCUS logDispatchStateLocked(); @@ -2305,6 +2473,70 @@ void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { } } +bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) { +#if DEBUG_FOCUS + LOGD("transferTouchFocus: fromChannel=%s, toChannel=%s", + fromChannel->getName().string(), toChannel->getName().string()); +#endif + { // acquire lock + AutoMutex _l(mLock); + + const InputWindow* fromWindow = getWindowLocked(fromChannel); + const InputWindow* toWindow = getWindowLocked(toChannel); + if (! fromWindow || ! toWindow) { +#if DEBUG_FOCUS + LOGD("Cannot transfer focus because from or to window not found."); +#endif + return false; + } + if (fromWindow == toWindow) { +#if DEBUG_FOCUS + LOGD("Trivial transfer to same window."); +#endif + return true; + } + + bool found = false; + for (size_t i = 0; i < mTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTouchState.windows[i]; + if (touchedWindow.window == fromWindow) { + int32_t oldTargetFlags = touchedWindow.targetFlags; + BitSet32 pointerIds = touchedWindow.pointerIds; + + mTouchState.windows.removeAt(i); + + int32_t newTargetFlags = 0; + if (oldTargetFlags & InputTarget::FLAG_FOREGROUND) { + newTargetFlags |= InputTarget::FLAG_FOREGROUND; + if (toWindow->layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH) { + newTargetFlags |= InputTarget::FLAG_SPLIT; + } + } + mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds); + + found = true; + break; + } + } + + if (! found) { +#if DEBUG_FOCUS + LOGD("Focus transfer failed because from window did not have focus."); +#endif + return false; + } + +#if DEBUG_FOCUS + logDispatchStateLocked(); +#endif + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mLooper->wake(); + return true; +} + void InputDispatcher::logDispatchStateLocked() { String8 dump; dumpDispatchStateLocked(dump); @@ -2334,12 +2566,13 @@ void InputDispatcher::dumpDispatchStateLocked(String8& dump) { } dump.appendFormat(" focusedWindow: name='%s'\n", mFocusedWindow != NULL ? mFocusedWindow->name.string() : "<null>"); - dump.appendFormat(" touchedWindow: name='%s', touchDown=%d\n", - mTouchedWindow != NULL ? mTouchedWindow->name.string() : "<null>", - mTouchDown); - for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) { - dump.appendFormat(" touchedWallpaperWindows[%d]: name='%s'\n", - i, mTouchedWallpaperWindows[i]->name.string()); + dump.appendFormat(" touchState: down=%s, split=%s\n", toString(mTouchState.down), + toString(mTouchState.split)); + for (size_t i = 0; i < mTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTouchState.windows[i]; + dump.appendFormat(" touchedWindow[%d]: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n", + i, touchedWindow.window->name.string(), touchedWindow.pointerIds.value, + touchedWindow.targetFlags); } for (size_t i = 0; i < mWindows.size(); i++) { dump.appendFormat(" windows[%d]: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, " @@ -2594,8 +2827,7 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) { mLock.unlock(); - mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->windowType, - commandEntry->userActivityEventType); + mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType); mLock.lock(); } @@ -2627,17 +2859,32 @@ uint32_t InputDispatcher::Queue<T>::count() const { InputDispatcher::Allocator::Allocator() { } +InputDispatcher::InjectionState* +InputDispatcher::Allocator::obtainInjectionState(int32_t injectorPid, int32_t injectorUid) { + InjectionState* injectionState = mInjectionStatePool.alloc(); + injectionState->refCount = 1; + injectionState->injectorPid = injectorPid; + injectionState->injectorUid = injectorUid; + injectionState->injectionIsAsync = false; + injectionState->injectionResult = INPUT_EVENT_INJECTION_PENDING; + injectionState->pendingForegroundDispatches = 0; + return injectionState; +} + void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime) { entry->type = type; entry->refCount = 1; entry->dispatchInProgress = false; entry->eventTime = eventTime; - entry->injectionResult = INPUT_EVENT_INJECTION_PENDING; - entry->injectionIsAsync = false; - entry->injectorPid = -1; - entry->injectorUid = -1; - entry->pendingForegroundDispatches = 0; + entry->injectionState = NULL; +} + +void InputDispatcher::Allocator::releaseEventEntryInjectionState(EventEntry* entry) { + if (entry->injectionState) { + releaseInjectionState(entry->injectionState); + entry->injectionState = NULL; + } } InputDispatcher::ConfigurationChangedEntry* @@ -2720,6 +2967,15 @@ InputDispatcher::CommandEntry* InputDispatcher::Allocator::obtainCommandEntry(Co return entry; } +void InputDispatcher::Allocator::releaseInjectionState(InjectionState* injectionState) { + injectionState->refCount -= 1; + if (injectionState->refCount == 0) { + mInjectionStatePool.free(injectionState); + } else { + assert(injectionState->refCount > 0); + } +} + void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) { switch (entry->type) { case EventEntry::TYPE_CONFIGURATION_CHANGED: @@ -2741,6 +2997,7 @@ void InputDispatcher::Allocator::releaseConfigurationChangedEntry( ConfigurationChangedEntry* entry) { entry->refCount -= 1; if (entry->refCount == 0) { + releaseEventEntryInjectionState(entry); mConfigurationChangeEntryPool.free(entry); } else { assert(entry->refCount > 0); @@ -2750,6 +3007,7 @@ void InputDispatcher::Allocator::releaseConfigurationChangedEntry( void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) { entry->refCount -= 1; if (entry->refCount == 0) { + releaseEventEntryInjectionState(entry); mKeyEntryPool.free(entry); } else { assert(entry->refCount > 0); @@ -2759,6 +3017,7 @@ void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) { void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) { entry->refCount -= 1; if (entry->refCount == 0) { + releaseEventEntryInjectionState(entry); for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) { MotionSample* next = sample->next; mMotionSamplePool.free(sample); @@ -2793,22 +3052,12 @@ void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, motionEntry->lastSample = sample; } +void InputDispatcher::Allocator::recycleKeyEntry(KeyEntry* keyEntry) { + releaseEventEntryInjectionState(keyEntry); -// --- InputDispatcher::EventEntry --- - -void InputDispatcher::EventEntry::recycle() { - injectionResult = INPUT_EVENT_INJECTION_PENDING; - dispatchInProgress = false; - pendingForegroundDispatches = 0; -} - - -// --- InputDispatcher::KeyEntry --- - -void InputDispatcher::KeyEntry::recycle() { - EventEntry::recycle(); - syntheticRepeat = false; - interceptKeyResult = INTERCEPT_KEY_RESULT_UNKNOWN; + keyEntry->dispatchInProgress = false; + keyEntry->syntheticRepeat = false; + keyEntry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; } @@ -3057,6 +3306,72 @@ InputDispatcher::CommandEntry::~CommandEntry() { } +// --- InputDispatcher::TouchState --- + +InputDispatcher::TouchState::TouchState() : + down(false), split(false) { +} + +InputDispatcher::TouchState::~TouchState() { +} + +void InputDispatcher::TouchState::reset() { + down = false; + split = false; + windows.clear(); +} + +void InputDispatcher::TouchState::copyFrom(const TouchState& other) { + down = other.down; + split = other.split; + windows.clear(); + windows.appendVector(other.windows); +} + +void InputDispatcher::TouchState::addOrUpdateWindow(const InputWindow* window, + int32_t targetFlags, BitSet32 pointerIds) { + if (targetFlags & InputTarget::FLAG_SPLIT) { + split = true; + } + + for (size_t i = 0; i < windows.size(); i++) { + TouchedWindow& touchedWindow = windows.editItemAt(i); + if (touchedWindow.window == window) { + touchedWindow.targetFlags |= targetFlags; + touchedWindow.pointerIds.value |= pointerIds.value; + return; + } + } + + windows.push(); + + TouchedWindow& touchedWindow = windows.editTop(); + touchedWindow.window = window; + touchedWindow.targetFlags = targetFlags; + touchedWindow.pointerIds = pointerIds; + touchedWindow.channel = window->inputChannel; +} + +void InputDispatcher::TouchState::removeOutsideTouchWindows() { + for (size_t i = 0 ; i < windows.size(); ) { + if (windows[i].targetFlags & InputTarget::FLAG_OUTSIDE) { + windows.removeAt(i); + } else { + i += 1; + } + } +} + +const InputWindow* InputDispatcher::TouchState::getFirstForegroundWindow() { + for (size_t i = 0; i < windows.size(); i++) { + if (windows[i].targetFlags & InputTarget::FLAG_FOREGROUND) { + return windows[i].window; + } + } + return NULL; +} + + // --- InputDispatcherThread --- InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) : diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp index 783cbc4..f2b029a 100644 --- a/libs/ui/InputReader.cpp +++ b/libs/ui/InputReader.cpp @@ -3366,7 +3366,7 @@ void MultiTouchInputMapper::sync(nsecs_t when) { if (id > MAX_POINTER_ID) { #if DEBUG_POINTERS LOGD("Pointers: Ignoring driver provided pointer id %d because " - "it is larger than max supported id %d for optimizations", + "it is larger than max supported id %d", id, MAX_POINTER_ID); #endif havePointerIds = false; diff --git a/location/Android.mk b/location/Android.mk new file mode 100644 index 0000000..12db2f7 --- /dev/null +++ b/location/Android.mk @@ -0,0 +1,19 @@ +# 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) + +ifeq ($(TARGET_BUILD_APPS),) +include $(call all-makefiles-under, $(LOCAL_PATH)) +endif diff --git a/location/lib/Android.mk b/location/lib/Android.mk new file mode 100644 index 0000000..a06478a --- /dev/null +++ b/location/lib/Android.mk @@ -0,0 +1,45 @@ +# +# 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) + +# the library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_MODULE:= com.android.location.provider +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := \ + $(call all-subdir-java-files) + +include $(BUILD_JAVA_LIBRARY) + + +# ==== com.google.location.xml lib def ======================== +include $(CLEAR_VARS) + +LOCAL_MODULE := com.android.location.provider.xml +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE_CLASS := ETC + +# This will install the file in /system/etc/permissions +# +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions + +LOCAL_SRC_FILES := $(LOCAL_MODULE) + +include $(BUILD_PREBUILT) diff --git a/location/lib/com.android.location.provider.xml b/location/lib/com.android.location.provider.xml new file mode 100644 index 0000000..000e68f --- /dev/null +++ b/location/lib/com.android.location.provider.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<permissions> + <library name="com.android.location.provider" + file="/system/framework/com.android.location.provider.jar" /> +</permissions> diff --git a/location/java/android/location/provider/GeocodeProvider.java b/location/lib/java/com/android/location/provider/GeocodeProvider.java index 493c631..666bb02 100644 --- a/location/java/android/location/provider/GeocodeProvider.java +++ b/location/lib/java/com/android/location/provider/GeocodeProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.location.provider; +package com.android.location.provider; import android.os.IBinder; @@ -80,4 +80,3 @@ public abstract class GeocodeProvider { return mProvider; } } - diff --git a/location/java/android/location/provider/LocationProvider.java b/location/lib/java/com/android/location/provider/LocationProvider.java index 14dea14..3714f40 100644 --- a/location/java/android/location/provider/LocationProvider.java +++ b/location/lib/java/com/android/location/provider/LocationProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.location.provider; +package com.android.location.provider; import android.content.Context; import android.net.NetworkInfo; @@ -356,4 +356,3 @@ public abstract class LocationProvider { */ public abstract void onRemoveListener(int uid, WorkSource ws); } - diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 41d2cc5..6aa1ae6 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -16,18 +16,19 @@ package android.media; -import java.util.NoSuchElementException; import android.app.ActivityManagerNative; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.media.MediaPlayer.OnCompletionListener; @@ -40,6 +41,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.provider.Settings; import android.provider.Settings.System; import android.telephony.PhoneStateListener; @@ -47,7 +49,6 @@ import android.telephony.TelephonyManager; import android.util.Log; import android.view.KeyEvent; import android.view.VolumePanel; -import android.os.SystemProperties; import com.android.internal.telephony.ITelephony; @@ -58,6 +59,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; @@ -258,8 +260,8 @@ public class AudioService extends IAudioService.Stub { // BluetoothHeadset API to control SCO connection private BluetoothHeadset mBluetoothHeadset; - // Bluetooth headset connection state - private boolean mBluetoothHeadsetConnected; + // Bluetooth headset device + private BluetoothDevice mBluetoothHeadsetDevice; /////////////////////////////////////////////////////////////////////////// // Construction @@ -294,17 +296,20 @@ public class AudioService extends IAudioService.Stub { AudioSystem.setErrorCallback(mAudioSystemCallback); loadSoundEffects(); - mBluetoothHeadsetConnected = false; - mBluetoothHeadset = new BluetoothHeadset(context, - mBluetoothHeadsetServiceListener); + mBluetoothHeadsetDevice = null; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEADSET); + } // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); - intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); - intentFilter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); - intentFilter.addAction(Intent.ACTION_DOCK_EVENT); + intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); + intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + intentFilter.addAction(Intent.ACTION_DOCK_EVENT); context.registerReceiver(mReceiver, intentFilter); // Register for media button intent broadcasts. @@ -1000,7 +1005,7 @@ public class AudioService extends IAudioService.Stub { public void incCount() { synchronized(mScoClients) { - requestScoState(BluetoothHeadset.AUDIO_STATE_CONNECTED); + requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED); if (mStartcount == 0) { try { mCb.linkToDeath(this, 0); @@ -1026,7 +1031,7 @@ public class AudioService extends IAudioService.Stub { Log.w(TAG, "decCount() going to 0 but not registered to binder"); } } - requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED); + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); } } } @@ -1042,7 +1047,7 @@ public class AudioService extends IAudioService.Stub { } mStartcount = 0; if (stopSco) { - requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED); + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); } } } @@ -1068,12 +1073,12 @@ public class AudioService extends IAudioService.Stub { private void requestScoState(int state) { if (totalCount() == 0 && - mBluetoothHeadsetConnected && + mBluetoothHeadsetDevice != null && AudioService.this.mMode == AudioSystem.MODE_NORMAL) { - if (state == BluetoothHeadset.AUDIO_STATE_CONNECTED) { - mBluetoothHeadset.startVoiceRecognition(); + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + mBluetoothHeadset.startVoiceRecognition(mBluetoothHeadsetDevice); } else { - mBluetoothHeadset.stopVoiceRecognition(); + mBluetoothHeadset.stopVoiceRecognition(mBluetoothHeadsetDevice); } } } @@ -1103,23 +1108,27 @@ public class AudioService extends IAudioService.Stub { } } - private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener = - new BluetoothHeadset.ServiceListener() { - public void onServiceConnected() { - if (mBluetoothHeadset != null) { - BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset(); - if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_CONNECTED) { - mBluetoothHeadsetConnected = true; - } + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothHeadset = (BluetoothHeadset) proxy; + Set<BluetoothDevice> deviceSet = mBluetoothHeadset.getConnectedDevices(); + if (deviceSet.size() > 0) { + BluetoothDevice[] devices = + deviceSet.toArray(new BluetoothDevice[deviceSet.size()]); + mBluetoothHeadsetDevice = devices[0]; + } else { + mBluetoothHeadsetDevice = null; } } - public void onServiceDisconnected() { + public void onServiceDisconnected(int profile) { if (mBluetoothHeadset != null) { - BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset(); - if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_DISCONNECTED) { - mBluetoothHeadsetConnected = false; + Set<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices(); + if (devices.size() == 0) { + mBluetoothHeadsetDevice = null; clearAllScoClients(); } + mBluetoothHeadset = null; } } }; @@ -1813,18 +1822,18 @@ public class AudioService extends IAudioService.Stub { config = AudioSystem.FORCE_NONE; } AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config); - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, - BluetoothA2dp.STATE_DISCONNECTED); + } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, + BluetoothProfile.STATE_DISCONNECTED); BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); String address = btDevice.getAddress(); - boolean isConnected = (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && - ((String)mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)).equals(address)); + boolean isConnected = + (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && + mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address)); - if (isConnected && - state != BluetoothA2dp.STATE_CONNECTED && state != BluetoothA2dp.STATE_PLAYING) { + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { if (btDevice.isBluetoothDock()) { - if (state == BluetoothA2dp.STATE_DISCONNECTED) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { // introduction of a delay for transient disconnections of docks when // power is rapidly turned off/on, this message will be canceled if // we reconnect the dock under a preset delay @@ -1834,9 +1843,7 @@ public class AudioService extends IAudioService.Stub { } else { makeA2dpDeviceUnavailableNow(address); } - } else if (!isConnected && - (state == BluetoothA2dp.STATE_CONNECTED || - state == BluetoothA2dp.STATE_PLAYING)) { + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { if (btDevice.isBluetoothDock()) { // this could be a reconnection after a transient disconnection cancelA2dpDeviceTimeout(); @@ -1851,9 +1858,9 @@ public class AudioService extends IAudioService.Stub { } makeA2dpDeviceAvailable(address); } - } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, - BluetoothHeadset.STATE_ERROR); + } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, + BluetoothProfile.STATE_DISCONNECTED); int device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); String address = null; @@ -1874,21 +1881,21 @@ public class AudioService extends IAudioService.Stub { } boolean isConnected = (mConnectedDevices.containsKey(device) && - ((String)mConnectedDevices.get(device)).equals(address)); + mConnectedDevices.get(device).equals(address)); - if (isConnected && state != BluetoothHeadset.STATE_CONNECTED) { + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, address); mConnectedDevices.remove(device); - mBluetoothHeadsetConnected = false; + mBluetoothHeadsetDevice = null; clearAllScoClients(); - } else if (!isConnected && state == BluetoothHeadset.STATE_CONNECTED) { + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, address); mConnectedDevices.put(new Integer(device), address); - mBluetoothHeadsetConnected = true; + mBluetoothHeadsetDevice = btDevice; } } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) { int state = intent.getIntExtra("state", 0); @@ -1922,15 +1929,14 @@ public class AudioService extends IAudioService.Stub { } } } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothHeadset.EXTRA_AUDIO_STATE, - BluetoothHeadset.STATE_ERROR); + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); synchronized (mScoClients) { if (!mScoClients.isEmpty()) { switch (state) { - case BluetoothHeadset.AUDIO_STATE_CONNECTED: + case BluetoothHeadset.STATE_AUDIO_CONNECTED: state = AudioManager.SCO_AUDIO_STATE_CONNECTED; break; - case BluetoothHeadset.AUDIO_STATE_DISCONNECTED: + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; break; default: diff --git a/media/java/android/media/MtpCursor.java b/media/java/android/media/MtpCursor.java index b9dd03e..ff8799a 100644 --- a/media/java/android/media/MtpCursor.java +++ b/media/java/android/media/MtpCursor.java @@ -31,7 +31,7 @@ public final class MtpCursor extends AbstractWindowedCursor { static final String TAG = "MtpCursor"; static final int NO_COUNT = -1; - /* constants for mQueryType */ + /* constants for queryType */ public static final int DEVICE = 1; public static final int DEVICE_ID = 2; public static final int STORAGE = 3; @@ -41,27 +41,15 @@ public final class MtpCursor extends AbstractWindowedCursor { public static final int STORAGE_CHILDREN = 7; public static final int OBJECT_CHILDREN = 8; - private int mQueryType; - private int mDeviceID; - private long mStorageID; - private long mQbjectID; - /** The names of the columns in the projection */ private String[] mColumns; /** The number of rows in the cursor */ private int mCount = NO_COUNT; - private final MtpClient mClient; public MtpCursor(MtpClient client, int queryType, int deviceID, long storageID, long objectID, String[] projection) { - - mClient = client; - mQueryType = queryType; - mDeviceID = deviceID; - mStorageID = storageID; - mQbjectID = objectID; mColumns = projection; HashMap<String, Integer> map; diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java index 403ed58..630d7112e 100644 --- a/media/java/android/media/MtpDatabase.java +++ b/media/java/android/media/MtpDatabase.java @@ -25,8 +25,9 @@ import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.RemoteException; import android.provider.MediaStore.Audio; -import android.provider.MediaStore.MediaColumns; import android.provider.MediaStore.Files; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.MediaColumns; import android.provider.Mtp; import android.util.Log; @@ -278,8 +279,9 @@ public class MtpDatabase { return null; } - private int[] getSupportedObjectProperties(int handle) { - return new int[] { + static final int[] FILE_PROPERTIES = { + // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES + // and IMAGE_PROPERTIES below MtpConstants.PROPERTY_STORAGE_ID, MtpConstants.PROPERTY_OBJECT_FORMAT, MtpConstants.PROPERTY_PROTECTION_STATUS, @@ -289,7 +291,93 @@ public class MtpDatabase { MtpConstants.PROPERTY_PARENT_OBJECT, MtpConstants.PROPERTY_PERSISTENT_UID, MtpConstants.PROPERTY_NAME, - }; + MtpConstants.PROPERTY_DATE_ADDED, + }; + + static final int[] AUDIO_PROPERTIES = { + // NOTE must match FILE_PROPERTIES above + MtpConstants.PROPERTY_STORAGE_ID, + MtpConstants.PROPERTY_OBJECT_FORMAT, + MtpConstants.PROPERTY_PROTECTION_STATUS, + MtpConstants.PROPERTY_OBJECT_SIZE, + MtpConstants.PROPERTY_OBJECT_FILE_NAME, + MtpConstants.PROPERTY_DATE_MODIFIED, + MtpConstants.PROPERTY_PARENT_OBJECT, + MtpConstants.PROPERTY_PERSISTENT_UID, + MtpConstants.PROPERTY_NAME, + MtpConstants.PROPERTY_DISPLAY_NAME, + MtpConstants.PROPERTY_DATE_ADDED, + + // audio specific properties + MtpConstants.PROPERTY_ARTIST, + MtpConstants.PROPERTY_ALBUM_NAME, + MtpConstants.PROPERTY_ALBUM_ARTIST, + MtpConstants.PROPERTY_TRACK, + MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, + MtpConstants.PROPERTY_DURATION, + MtpConstants.PROPERTY_GENRE, + MtpConstants.PROPERTY_COMPOSER, + }; + + static final int[] VIDEO_PROPERTIES = { + // NOTE must match FILE_PROPERTIES above + MtpConstants.PROPERTY_STORAGE_ID, + MtpConstants.PROPERTY_OBJECT_FORMAT, + MtpConstants.PROPERTY_PROTECTION_STATUS, + MtpConstants.PROPERTY_OBJECT_SIZE, + MtpConstants.PROPERTY_OBJECT_FILE_NAME, + MtpConstants.PROPERTY_DATE_MODIFIED, + MtpConstants.PROPERTY_PARENT_OBJECT, + MtpConstants.PROPERTY_PERSISTENT_UID, + MtpConstants.PROPERTY_NAME, + MtpConstants.PROPERTY_DISPLAY_NAME, + MtpConstants.PROPERTY_DATE_ADDED, + + // video specific properties + MtpConstants.PROPERTY_ARTIST, + MtpConstants.PROPERTY_ALBUM_NAME, + MtpConstants.PROPERTY_DURATION, + MtpConstants.PROPERTY_DESCRIPTION, + }; + + static final int[] IMAGE_PROPERTIES = { + // NOTE must match FILE_PROPERTIES above + MtpConstants.PROPERTY_STORAGE_ID, + MtpConstants.PROPERTY_OBJECT_FORMAT, + MtpConstants.PROPERTY_PROTECTION_STATUS, + MtpConstants.PROPERTY_OBJECT_SIZE, + MtpConstants.PROPERTY_OBJECT_FILE_NAME, + MtpConstants.PROPERTY_DATE_MODIFIED, + MtpConstants.PROPERTY_PARENT_OBJECT, + MtpConstants.PROPERTY_PERSISTENT_UID, + MtpConstants.PROPERTY_NAME, + MtpConstants.PROPERTY_DISPLAY_NAME, + MtpConstants.PROPERTY_DATE_ADDED, + + // image specific properties + MtpConstants.PROPERTY_DESCRIPTION, + }; + + private int[] getSupportedObjectProperties(int format) { + switch (format) { + case MtpConstants.FORMAT_MP3: + case MtpConstants.FORMAT_WAV: + case MtpConstants.FORMAT_WMA: + case MtpConstants.FORMAT_OGG: + case MtpConstants.FORMAT_AAC: + return AUDIO_PROPERTIES; + case MtpConstants.FORMAT_MPEG: + case MtpConstants.FORMAT_3GP_CONTAINER: + case MtpConstants.FORMAT_WMV: + return VIDEO_PROPERTIES; + case MtpConstants.FORMAT_EXIF_JPEG: + case MtpConstants.FORMAT_GIF: + case MtpConstants.FORMAT_PNG: + case MtpConstants.FORMAT_BMP: + return IMAGE_PROPERTIES; + default: + return FILE_PROPERTIES; + } } private int[] getSupportedDeviceProperties() { @@ -299,17 +387,90 @@ public class MtpDatabase { }; } + private String queryString(int id, String column) { + Cursor c = null; + try { + // for now we are only reading properties from the "objects" table + c = mMediaProvider.query(mObjectsUri, + new String [] { Files.FileColumns._ID, column }, + ID_WHERE, new String[] { Integer.toString(id) }, null); + if (c != null && c.moveToNext()) { + return c.getString(1); + } else { + return ""; + } + } catch (Exception e) { + return null; + } finally { + if (c != null) { + c.close(); + } + } + } + + private String queryGenre(int id) { + Cursor c = null; + try { + Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id); + c = mMediaProvider.query(uri, + new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME }, + null, null, null); + if (c != null && c.moveToNext()) { + return c.getString(1); + } else { + return ""; + } + } catch (Exception e) { + Log.e(TAG, "queryGenre exception", e); + return null; + } finally { + if (c != null) { + c.close(); + } + } + } + + private boolean queryInt(int id, String column, long[] outValue) { + Cursor c = null; + try { + // for now we are only reading properties from the "objects" table + c = mMediaProvider.query(mObjectsUri, + new String [] { Files.FileColumns._ID, column }, + ID_WHERE, new String[] { Integer.toString(id) }, null); + if (c != null && c.moveToNext()) { + outValue[0] = c.getLong(1); + return true; + } + return false; + } catch (Exception e) { + return false; + } finally { + if (c != null) { + c.close(); + } + } + } + + private String nameFromPath(String path) { + // extract name from full path + int start = 0; + int lastSlash = path.lastIndexOf('/'); + if (lastSlash >= 0) { + start = lastSlash + 1; + } + int end = path.length(); + if (end - start > 255) { + end = start + 255; + } + return path.substring(start, end); + } + private int getObjectProperty(int handle, int property, long[] outIntValue, char[] outStringValue) { Log.d(TAG, "getObjectProperty: " + property); String column = null; boolean isString = false; - // temporary hack - if (property == MtpConstants.PROPERTY_NAME) { - property = MtpConstants.PROPERTY_OBJECT_FILE_NAME; - } - switch (property) { case MtpConstants.PROPERTY_STORAGE_ID: outIntValue[0] = mStorageID; @@ -325,12 +486,46 @@ public class MtpDatabase { column = Files.FileColumns.SIZE; break; case MtpConstants.PROPERTY_OBJECT_FILE_NAME: - column = Files.FileColumns.DATA; - isString = true; - break; + // special case - need to extract file name from full path + String value = queryString(handle, Files.FileColumns.DATA); + if (value != null) { + value = nameFromPath(value); + value.getChars(0, value.length(), outStringValue, 0); + outStringValue[value.length()] = 0; + return MtpConstants.RESPONSE_OK; + } else { + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + } + case MtpConstants.PROPERTY_NAME: + // first try title + String name = queryString(handle, MediaColumns.TITLE); + // then try name + if (name == null) { + name = queryString(handle, Audio.PlaylistsColumns.NAME); + } + // if title and name fail, extract name from full path + if (name == null) { + name = queryString(handle, Files.FileColumns.DATA); + if (name != null) { + name = nameFromPath(name); + } + } + if (name != null) { + name.getChars(0, name.length(), outStringValue, 0); + outStringValue[name.length()] = 0; + return MtpConstants.RESPONSE_OK; + } else { + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + } case MtpConstants.PROPERTY_DATE_MODIFIED: column = Files.FileColumns.DATE_MODIFIED; break; + case MtpConstants.PROPERTY_DATE_ADDED: + column = Files.FileColumns.DATE_ADDED; + break; + case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: + column = Audio.AudioColumns.YEAR; + break; case MtpConstants.PROPERTY_PARENT_OBJECT: column = Files.FileColumns.PARENT; break; @@ -341,44 +536,64 @@ public class MtpDatabase { puid += handle; outIntValue[0] = puid; return MtpConstants.RESPONSE_OK; + case MtpConstants.PROPERTY_DURATION: + column = Audio.AudioColumns.DURATION; + break; + case MtpConstants.PROPERTY_TRACK: + if (queryInt(handle, Audio.AudioColumns.TRACK, outIntValue)) { + // track is stored in lower 3 decimal digits + outIntValue[0] %= 1000; + return MtpConstants.RESPONSE_OK; + } else { + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + } + case MtpConstants.PROPERTY_DISPLAY_NAME: + column = MediaColumns.DISPLAY_NAME; + isString = true; + break; + case MtpConstants.PROPERTY_ARTIST: + column = Audio.AudioColumns.ARTIST; + isString = true; + break; + case MtpConstants.PROPERTY_ALBUM_NAME: + column = Audio.AudioColumns.ALBUM; + isString = true; + break; + case MtpConstants.PROPERTY_ALBUM_ARTIST: + column = Audio.AudioColumns.ALBUM_ARTIST; + isString = true; + break; + case MtpConstants.PROPERTY_GENRE: + String genre = queryGenre(handle); + if (genre != null) { + genre.getChars(0, genre.length(), outStringValue, 0); + outStringValue[genre.length()] = 0; + return MtpConstants.RESPONSE_OK; + } else { + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + } + case MtpConstants.PROPERTY_COMPOSER: + column = Audio.AudioColumns.COMPOSER; + isString = true; + break; + case MtpConstants.PROPERTY_DESCRIPTION: + column = Images.ImageColumns.DESCRIPTION; + isString = true; + break; default: return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; } - Cursor c = null; - try { - // for now we are only reading properties from the "objects" table - c = mMediaProvider.query(mObjectsUri, - new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(handle) }, null); - if (c != null && c.moveToNext()) { - if (isString) { - String value = c.getString(1); - int start = 0; - - if (property == MtpConstants.PROPERTY_OBJECT_FILE_NAME) { - // extract name from full path - int lastSlash = value.lastIndexOf('/'); - if (lastSlash >= 0) { - start = lastSlash + 1; - } - } - int end = value.length(); - if (end - start > 255) { - end = start + 255; - } - value.getChars(start, end, outStringValue, 0); - outStringValue[end - start] = 0; - } else { - outIntValue[0] = c.getLong(1); - } + if (isString) { + String value = queryString(handle, column); + if (value != null) { + value.getChars(0, value.length(), outStringValue, 0); + outStringValue[value.length()] = 0; return MtpConstants.RESPONSE_OK; } - } catch (Exception e) { - return MtpConstants.RESPONSE_GENERAL_ERROR; - } finally { - if (c != null) { - c.close(); + } else { + if (queryInt(handle, column, outIntValue)) { + return MtpConstants.RESPONSE_OK; } } // query failed if we get here diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java index 9d40a78..468ba2a 100755 --- a/media/java/android/media/videoeditor/AudioTrack.java +++ b/media/java/android/media/videoeditor/AudioTrack.java @@ -35,6 +35,11 @@ public class AudioTrack { private long mEndBoundaryTimeMs;
private boolean mLoop;
+ private final int mAudioChannels;
+ private final int mAudioType;
+ private final int mAudioBitrate;
+ private final int mAudioSamplingFrequency;
+
// Ducking variables
private int mDuckingThreshold;
private int mDuckingLowVolume;
@@ -54,7 +59,6 @@ public class AudioTrack { /**
* Constructor
- *
* @param audioTrackId The AudioTrack id
* @param filename The absolute file name
*
@@ -68,6 +72,13 @@ public class AudioTrack { mStartTimeMs = 0;
// TODO: This value represents to the duration of the audio file
mDurationMs = 300000;
+ // TODO: This value needs to be read from the audio track of the source
+ // file
+ mAudioChannels = 2;
+ mAudioType = MediaProperties.ACODEC_AAC_LC;
+ mAudioBitrate = 128000;
+ mAudioSamplingFrequency = 44100;
+
mTimelineDurationMs = mDurationMs;
mVolumePercent = 100;
@@ -104,6 +115,34 @@ public class AudioTrack { }
/**
+ * @return The number of audio channels in the source of this audio track
+ */
+ public int getAudioChannels() {
+ return mAudioChannels;
+ }
+
+ /**
+ * @return The audio codec of the source of this audio track
+ */
+ public int getAudioType() {
+ return mAudioType;
+ }
+
+ /**
+ * @return The audio sample frequency of the audio track
+ */
+ public int getAudioSamplingFrequency() {
+ return mAudioSamplingFrequency;
+ }
+
+ /**
+ * @return The audio bitrate of the audio track
+ */
+ public int getAudioBitrate() {
+ return mAudioBitrate;
+ }
+
+ /**
* Set the volume of this audio track as percentage of the volume in the
* original audio source file.
*
@@ -112,8 +151,8 @@ public class AudioTrack { * is same as original volume. It it is set to 200, then volume
* is doubled (provided that volume amplification is supported)
*
- * @throws UnsupportedOperationException if volume amplification is requested
- * and is not supported.
+ * @throws UnsupportedOperationException if volume amplification is
+ * requested and is not supported.
*/
public void setVolume(int volumePercent) {
mVolumePercent = volumePercent;
@@ -209,8 +248,8 @@ public class AudioTrack { /**
* Enable the loop mode for this audio track. Note that only one of the
- * audio tracks in the timeline can have the loop mode enabled. When
- * looping is enabled the samples between mBeginBoundaryTimeMs and
+ * audio tracks in the timeline can have the loop mode enabled. When looping
+ * is enabled the samples between mBeginBoundaryTimeMs and
* mEndBoundaryTimeMs are looped.
*/
public void enableLoop() {
@@ -285,18 +324,19 @@ public class AudioTrack { */
public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
throws IOException {
- // TODO: Set mAudioWaveformFilename at the end once the extract is complete
+ // TODO: Set mAudioWaveformFilename at the end once the extract is
+ // complete
}
/**
* Get the audio waveform file name if extractAudioWaveform was successful.
* The file format is as following:
* <ul>
- * <li>first 4 bytes provide the number of samples for each value, as
- * big-endian signed</li>
- * <li>4 following bytes is the total number of values in the file, as
- * big-endian signed</li>
- * <li>then, all values follow as bytes</li>
+ * <li>first 4 bytes provide the number of samples for each value, as
+ * big-endian signed</li>
+ * <li>4 following bytes is the total number of values in the file, as
+ * big-endian signed</li>
+ * <li>then, all values follow as bytes</li>
* </ul>
*
* @return the name of the file, null if the file does not exist
diff --git a/media/java/android/media/videoeditor/Effect.java b/media/java/android/media/videoeditor/Effect.java index 8c39577..038bfc2 100755 --- a/media/java/android/media/videoeditor/Effect.java +++ b/media/java/android/media/videoeditor/Effect.java @@ -26,6 +26,8 @@ package android.media.videoeditor; public abstract class Effect {
// Instance variables
private final String mUniqueId;
+ // The effect owner
+ private final MediaItem mMediaItem;
protected long mDurationMs;
// The start time of the effect relative to the media item timeline
protected long mStartTimeMs;
@@ -35,6 +37,7 @@ public abstract class Effect { */
@SuppressWarnings("unused")
private Effect() {
+ mMediaItem = null;
mUniqueId = null;
mStartTimeMs = 0;
mDurationMs = 0;
@@ -43,12 +46,22 @@ public abstract class Effect { /**
* Constructor
*
+ * @param mediaItem The media item owner
* @param effectId The effect id
* @param startTimeMs The start time relative to the media item to which it
* is applied
* @param durationMs The effect duration in milliseconds
*/
- public Effect(String effectId, long startTimeMs, long durationMs) {
+ public Effect(MediaItem mediaItem, String effectId, long startTimeMs, long durationMs) {
+ if (mediaItem == null) {
+ throw new IllegalArgumentException("Media item cannot be null");
+ }
+
+ if (startTimeMs + durationMs > mediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Invalid start time and duration");
+ }
+
+ mMediaItem = mediaItem;
mUniqueId = effectId;
mStartTimeMs = startTimeMs;
mDurationMs = durationMs;
@@ -68,7 +81,13 @@ public abstract class Effect { * @param durationMs of the effect in milliseconds
*/
public void setDuration(long durationMs) {
+ if (mStartTimeMs + durationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Duration is too large");
+ }
+
mDurationMs = durationMs;
+
+ mMediaItem.invalidateTransitions(this);
}
/**
@@ -88,7 +107,13 @@ public abstract class Effect { * of the media item in milliseconds
*/
public void setStartTime(long startTimeMs) {
+ if (startTimeMs + mDurationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Start time is too large");
+ }
+
mStartTimeMs = startTimeMs;
+
+ mMediaItem.invalidateTransitions(this);
}
/**
@@ -98,6 +123,13 @@ public abstract class Effect { return mStartTimeMs;
}
+ /**
+ * @return The media item owner
+ */
+ public MediaItem getMediaItem() {
+ return mMediaItem;
+ }
+
/*
* {@inheritDoc}
*/
diff --git a/media/java/android/media/videoeditor/EffectColor.java b/media/java/android/media/videoeditor/EffectColor.java index 7c61627..f38cf75 100755 --- a/media/java/android/media/videoeditor/EffectColor.java +++ b/media/java/android/media/videoeditor/EffectColor.java @@ -61,12 +61,13 @@ public class EffectColor extends Effect { */
@SuppressWarnings("unused")
private EffectColor() {
- this(null, 0, 0, 0, 0);
+ this(null, null, 0, 0, 0, 0);
}
/**
* Constructor
*
+ * @param mediaItem The media item owner
* @param effectId The effect id
* @param startTimeMs The start time relative to the media item to which it
* is applied
@@ -77,9 +78,9 @@ public class EffectColor extends Effect { * @param param if type is TYPE_COLOR, param is the RGB color as 888.
* Otherwise, param is ignored
*/
- public EffectColor(String effectId, long startTimeMs, long durationMs,
+ public EffectColor(MediaItem mediaItem, String effectId, long startTimeMs, long durationMs,
int type, int param) {
- super(effectId, startTimeMs, durationMs);
+ super(mediaItem, effectId, startTimeMs, durationMs);
mType = type;
mParam = param;
}
diff --git a/media/java/android/media/videoeditor/EffectKenBurns.java b/media/java/android/media/videoeditor/EffectKenBurns.java index c6d22f4..fd2da5c 100755 --- a/media/java/android/media/videoeditor/EffectKenBurns.java +++ b/media/java/android/media/videoeditor/EffectKenBurns.java @@ -35,22 +35,23 @@ public class EffectKenBurns extends Effect { */
@SuppressWarnings("unused")
private EffectKenBurns() throws IOException {
- this(null, null, null, 0, 0);
+ this(null, null, null, null, 0, 0);
}
/**
* Constructor
*
+ * @param mediaItem The media item owner
* @param effectId The effect id
* @param startRect The start rectangle
* @param endRect The end rectangle
* @param startTimeMs The start time
* @param durationMs The duration of the Ken Burns effect in milliseconds
*/
- public EffectKenBurns(String effectId, Rect startRect, Rect endRect, long startTime,
- long durationMs)
+ public EffectKenBurns(MediaItem mediaItem, String effectId, Rect startRect, Rect endRect,
+ long startTime, long durationMs)
throws IOException {
- super(effectId, startTime, durationMs);
+ super(mediaItem, effectId, startTime, durationMs);
mStartRect = startRect;
mEndRect = endRect;
diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java index 9c39b20..ab23119 100755 --- a/media/java/android/media/videoeditor/MediaImageItem.java +++ b/media/java/android/media/videoeditor/MediaImageItem.java @@ -17,6 +17,7 @@ package android.media.videoeditor;
import java.io.IOException;
+import java.util.List;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -24,9 +25,14 @@ import android.graphics.Canvas; import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
+import android.util.Pair;
/**
- * This class represents an image item on the storyboard.
+ * This class represents an image item on the storyboard. Note that images are
+ * scaled down to the maximum supported resolution by preserving the native
+ * aspect ratio. To learn the scaled image dimensions use
+ * {@link #getScaledWidth()} and {@link #getScaledHeight()} respectively.
+ *
* {@hide}
*/
public class MediaImageItem extends MediaItem {
@@ -41,6 +47,7 @@ public class MediaImageItem extends MediaItem { private final int mHeight;
private final int mAspectRatio;
private long mDurationMs;
+ private int mScaledWidth, mScaledHeight;
/**
* This class cannot be instantiated by using the default constructor
@@ -53,7 +60,7 @@ public class MediaImageItem extends MediaItem { /**
* Constructor
*
- * @param mediaItemId The MediaItem id
+ * @param mediaItemId The media item id
* @param filename The image file name
* @param durationMs The duration of the image on the storyboard
* @param renderingMode The rendering mode
@@ -64,7 +71,7 @@ public class MediaImageItem extends MediaItem { throws IOException {
super(mediaItemId, filename, renderingMode);
- // Determine the size of the image
+ // Determine the dimensions of the image
final BitmapFactory.Options dbo = new BitmapFactory.Options();
dbo.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filename, dbo);
@@ -75,6 +82,22 @@ public class MediaImageItem extends MediaItem { // TODO: Determine the aspect ratio from the width and height
mAspectRatio = MediaProperties.ASPECT_RATIO_4_3;
+
+ // Images are stored in memory scaled to the maximum resolution to
+ // save memory.
+ final Pair<Integer, Integer>[] resolutions =
+ MediaProperties.getSupportedResolutions(mAspectRatio);
+ // Get the highest resolution
+ final Pair<Integer, Integer> maxResolution = resolutions[resolutions.length - 1];
+ if (mHeight > maxResolution.second) {
+ // We need to scale the image
+ scaleImage(filename, maxResolution.first, maxResolution.second);
+ mScaledWidth = maxResolution.first;
+ mScaledHeight = maxResolution.second;
+ } else {
+ mScaledWidth = mWidth;
+ mScaledHeight = mHeight;
+ }
}
/*
@@ -107,6 +130,20 @@ public class MediaImageItem extends MediaItem { return mHeight;
}
+ /**
+ * @return The scaled width of the image.
+ */
+ public int getScaledWidth() {
+ return mScaledWidth;
+ }
+
+ /**
+ * @return The scaled height of the image.
+ */
+ public int getScaledHeight() {
+ return mScaledHeight;
+ }
+
/*
* {@inheritDoc}
*/
@@ -140,15 +177,31 @@ public class MediaImageItem extends MediaItem { }
}
- // TODO: Validate/modify the start and the end time of effects and overlays
- }
+ final List<Overlay> overlays = getAllOverlays();
+ for (Overlay overlay : overlays) {
+ // Adjust the start time if necessary
+ if (overlay.getStartTime() < getTimelineDuration()) {
+ overlay.setStartTime(0);
+ }
- /*
- * {@inheritDoc}
- */
- @Override
- public long getDuration() {
- return mDurationMs;
+ // Adjust the duration if necessary
+ if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
+ overlay.setDuration(getTimelineDuration() - overlay.getStartTime());
+ }
+ }
+
+ final List<Effect> effects = getAllEffects();
+ for (Effect effect : effects) {
+ // Adjust the start time if necessary
+ if (effect.getStartTime() < getTimelineDuration()) {
+ effect.setStartTime(0);
+ }
+
+ // Adjust the duration if necessary
+ if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
+ effect.setDuration(getTimelineDuration() - effect.getStartTime());
+ }
+ }
}
/*
@@ -164,7 +217,7 @@ public class MediaImageItem extends MediaItem { */
@Override
public Bitmap getThumbnail(int width, int height, long timeMs) throws IOException {
- return generateImageThumbnail(mFilename, width, height);
+ return scaleImage(mFilename, width, height);
}
/*
@@ -173,7 +226,7 @@ public class MediaImageItem extends MediaItem { @Override
public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
int thumbnailCount) throws IOException {
- final Bitmap thumbnail = generateImageThumbnail(mFilename, width, height);
+ final Bitmap thumbnail = scaleImage(mFilename, width, height);
final Bitmap[] thumbnailArray = new Bitmap[thumbnailCount];
for (int i = 0; i < thumbnailCount; i++) {
thumbnailArray[i] = thumbnail;
@@ -182,7 +235,7 @@ public class MediaImageItem extends MediaItem { }
/**
- * Resize a bitmap within an input stream
+ * Resize a bitmap to the specified width and height
*
* @param filename The filename
* @param width The thumbnail width
@@ -190,7 +243,7 @@ public class MediaImageItem extends MediaItem { *
* @return The resized bitmap
*/
- private Bitmap generateImageThumbnail(String filename, int width, int height)
+ private Bitmap scaleImage(String filename, int width, int height)
throws IOException {
final BitmapFactory.Options dbo = new BitmapFactory.Options();
dbo.inJustDecodeBounds = true;
diff --git a/media/java/android/media/videoeditor/MediaItem.java b/media/java/android/media/videoeditor/MediaItem.java index 12fbe54..f4651af 100755 --- a/media/java/android/media/videoeditor/MediaItem.java +++ b/media/java/android/media/videoeditor/MediaItem.java @@ -158,11 +158,6 @@ public abstract class MediaItem { }
/**
- * @return The duration of the media item
- */
- public abstract long getDuration();
-
- /**
* @return The timeline duration. This is the actual duration in the
* timeline (trimmed duration)
*/
@@ -210,11 +205,15 @@ public abstract class MediaItem { * added.
*/
public void addEffect(Effect effect) {
+ if (effect.getMediaItem() != this) {
+ throw new IllegalArgumentException("Media item mismatch");
+ }
+
if (mEffects.contains(effect)) {
throw new IllegalArgumentException("Effect already exists: " + effect.getId());
}
- if (effect.getStartTime() + effect.getDuration() > getDuration()) {
+ if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
throw new IllegalArgumentException(
"Effect start time + effect duration > media clip duration");
}
@@ -283,11 +282,15 @@ public abstract class MediaItem { * the bitmap do not match the dimensions of the media item
*/
public void addOverlay(Overlay overlay) {
+ if (overlay.getMediaItem() != this) {
+ throw new IllegalArgumentException("Media item mismatch");
+ }
+
if (mOverlays.contains(overlay)) {
throw new IllegalArgumentException("Overlay already exists: " + overlay.getId());
}
- if (overlay.getStartTime() + overlay.getDuration() > getDuration()) {
+ if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
throw new IllegalArgumentException(
"Overlay start time + overlay duration > media clip duration");
}
@@ -299,9 +302,18 @@ public abstract class MediaItem { throw new IllegalArgumentException("Overlay bitmap not specified");
}
+ final int scaledWidth, scaledHeight;
+ if (this instanceof MediaVideoItem) {
+ scaledWidth = getWidth();
+ scaledHeight = getHeight();
+ } else {
+ scaledWidth = ((MediaImageItem)this).getScaledWidth();
+ scaledHeight = ((MediaImageItem)this).getScaledHeight();
+ }
+
// The dimensions of the overlay bitmap must be the same as the
// media item dimensions
- if (bitmap.getWidth() != getWidth() || bitmap.getHeight() != getHeight()) {
+ if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) {
throw new IllegalArgumentException(
"Bitmap dimensions must match media item dimensions");
}
@@ -424,7 +436,7 @@ public abstract class MediaItem { *
* @param effect The effect that was added or removed
*/
- private void invalidateTransitions(Effect effect) {
+ void invalidateTransitions(Effect effect) {
// Check if the effect overlaps with the beginning and end transitions
if (mBeginTransition != null) {
if (effect.getStartTime() < mBeginTransition.getDuration()) {
@@ -433,7 +445,7 @@ public abstract class MediaItem { }
if (mEndTransition != null) {
- if (effect.getStartTime() + effect.getDuration() > getDuration()
+ if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()
- mEndTransition.getDuration()) {
mEndTransition.invalidate();
}
@@ -445,7 +457,7 @@ public abstract class MediaItem { *
* @param overlay The effect that was added or removed
*/
- private void invalidateTransitions(Overlay overlay) {
+ void invalidateTransitions(Overlay overlay) {
// Check if the overlay overlaps with the beginning and end transitions
if (mBeginTransition != null) {
if (overlay.getStartTime() < mBeginTransition.getDuration()) {
@@ -454,7 +466,7 @@ public abstract class MediaItem { }
if (mEndTransition != null) {
- if (overlay.getStartTime() + overlay.getDuration() > getDuration()
+ if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()
- mEndTransition.getDuration()) {
mEndTransition.invalidate();
}
diff --git a/media/java/android/media/videoeditor/MediaProperties.java b/media/java/android/media/videoeditor/MediaProperties.java index c3f5ef7..34088fc 100755 --- a/media/java/android/media/videoeditor/MediaProperties.java +++ b/media/java/android/media/videoeditor/MediaProperties.java @@ -29,6 +29,7 @@ public class MediaProperties { public static final int HEIGHT_360 = 360;
public static final int HEIGHT_480 = 480;
public static final int HEIGHT_720 = 720;
+ public static final int HEIGHT_1080 = 1080;
// Supported aspect ratios
public static final int ASPECT_RATIO_UNDEFINED = 0;
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java index afca55c..47d4fa0 100755 --- a/media/java/android/media/videoeditor/MediaVideoItem.java +++ b/media/java/android/media/videoeditor/MediaVideoItem.java @@ -17,6 +17,7 @@ package android.media.videoeditor;
import java.io.IOException;
+import java.util.List;
import android.graphics.Bitmap;
import android.media.MediaRecorder;
@@ -81,7 +82,8 @@ public class MediaVideoItem extends MediaItem { private final PlaybackProgressListener mListener;
private final int mCallbackAfterFrameCount;
private final long mFromMs, mToMs;
- private boolean mRun, mLoop;
+ private boolean mRun;
+ private final boolean mLoop;
private long mPositionMs;
/**
@@ -222,8 +224,8 @@ public class MediaVideoItem extends MediaItem { String audioWaveformFilename) throws IOException {
super(mediaItemId, filename, renderingMode);
// TODO: Set these variables correctly
- mWidth = 0;
- mHeight = 0;
+ mWidth = 1080;
+ mHeight = 720;
mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;
mFileType = MediaProperties.FILE_MP4;
mVideoType = MediaRecorder.VideoEncoder.H264;
@@ -297,7 +299,31 @@ public class MediaVideoItem extends MediaItem { }
}
- // TODO: Validate/modify the start and the end time of effects and overlays
+ final List<Overlay> overlays = getAllOverlays();
+ for (Overlay overlay : overlays) {
+ // Adjust the start time if necessary
+ if (overlay.getStartTime() < mBeginBoundaryTimeMs) {
+ overlay.setStartTime(mBeginBoundaryTimeMs);
+ }
+
+ // Adjust the duration if necessary
+ if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
+ overlay.setDuration(getTimelineDuration() - overlay.getStartTime());
+ }
+ }
+
+ final List<Effect> effects = getAllEffects();
+ for (Effect effect : effects) {
+ // Adjust the start time if necessary
+ if (effect.getStartTime() < mBeginBoundaryTimeMs) {
+ effect.setStartTime(mBeginBoundaryTimeMs);
+ }
+
+ // Adjust the duration if necessary
+ if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
+ effect.setDuration(getTimelineDuration() - effect.getStartTime());
+ }
+ }
}
/**
@@ -374,10 +400,9 @@ public class MediaVideoItem extends MediaItem { return mHeight;
}
- /*
- * {@inheritDoc}
+ /**
+ * @return The duration of the video clip
*/
- @Override
public long getDuration() {
return mDurationMs;
}
diff --git a/media/java/android/media/videoeditor/Overlay.java b/media/java/android/media/videoeditor/Overlay.java index 5065636..d9e7f85 100755 --- a/media/java/android/media/videoeditor/Overlay.java +++ b/media/java/android/media/videoeditor/Overlay.java @@ -16,6 +16,9 @@ package android.media.videoeditor;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* This is the super class for all Overlay classes.
@@ -24,23 +27,27 @@ package android.media.videoeditor; public abstract class Overlay {
// Instance variables
private final String mUniqueId;
+ // The overlay owner
+ private final MediaItem mMediaItem;
+ // user attributes
+ private final Map<String, String> mUserAttributes;
protected long mStartTimeMs;
protected long mDurationMs;
+
/**
* Default constructor
*/
@SuppressWarnings("unused")
private Overlay() {
- mUniqueId = null;
- mStartTimeMs = 0;
- mDurationMs = 0;
+ this(null, null, 0, 0);
}
/**
* Constructor
*
+ * @param mediaItem The media item owner
* @param overlayId The overlay id
* @param startTimeMs The start time relative to the media item start time
* @param durationMs The duration
@@ -48,10 +55,20 @@ public abstract class Overlay { * @throws IllegalArgumentException if the file type is not PNG or the
* startTimeMs and durationMs are incorrect.
*/
- public Overlay(String overlayId, long startTimeMs, long durationMs) {
+ public Overlay(MediaItem mediaItem, String overlayId, long startTimeMs, long durationMs) {
+ if (mediaItem == null) {
+ throw new IllegalArgumentException("Media item cannot be null");
+ }
+
+ if (startTimeMs + durationMs > mediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Invalid start time and duration");
+ }
+
+ mMediaItem = mediaItem;
mUniqueId = overlayId;
mStartTimeMs = startTimeMs;
mDurationMs = durationMs;
+ mUserAttributes = new HashMap<String, String>();
}
/**
@@ -75,7 +92,13 @@ public abstract class Overlay { * @param durationMs The duration in milliseconds
*/
public void setDuration(long durationMs) {
+ if (mStartTimeMs + durationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Duration is too large");
+ }
+
mDurationMs = durationMs;
+
+ mMediaItem.invalidateTransitions(this);
}
/**
@@ -93,7 +116,37 @@ public abstract class Overlay { * @param startTimeMs start time in milliseconds
*/
public void setStartTime(long startTimeMs) {
+ if (startTimeMs + mDurationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Start time is too large");
+ }
+
mStartTimeMs = startTimeMs;
+
+ mMediaItem.invalidateTransitions(this);
+ }
+
+ /**
+ * @return The media item owner
+ */
+ public MediaItem getMediaItem() {
+ return mMediaItem;
+ }
+
+ /**
+ * Set a user attribute
+ *
+ * @param name The attribute name
+ * @param value The attribute value
+ */
+ public void setUserAttribute(String name, String value) {
+ mUserAttributes.put(name, value);
+ }
+
+ /**
+ * @return The user attributes
+ */
+ public Map<String, String> getUserAttributes() {
+ return mUserAttributes;
}
/*
diff --git a/media/java/android/media/videoeditor/OverlayFrame.java b/media/java/android/media/videoeditor/OverlayFrame.java index 3abac6c..c7dfc39 100755 --- a/media/java/android/media/videoeditor/OverlayFrame.java +++ b/media/java/android/media/videoeditor/OverlayFrame.java @@ -32,7 +32,7 @@ import android.graphics.Bitmap.CompressFormat; */
public class OverlayFrame extends Overlay {
// Instance variables
- private final Bitmap mBitmap;
+ private Bitmap mBitmap;
private String mFilename;
/**
@@ -41,12 +41,13 @@ public class OverlayFrame extends Overlay { */
@SuppressWarnings("unused")
private OverlayFrame() {
- this(null, (String)null, 0, 0);
+ this(null, null, (String)null, 0, 0);
}
/**
* Constructor for an OverlayFrame
*
+ * @param mediaItem The media item owner
* @param overlayId The overlay id
* @param bitmap The bitmap to be used as an overlay. The size of the
* bitmap must equal to the size of the media item to which it is
@@ -57,9 +58,9 @@ public class OverlayFrame extends Overlay { * @throws IllegalArgumentException if the file type is not PNG or the
* startTimeMs and durationMs are incorrect.
*/
- public OverlayFrame(String overlayId, Bitmap bitmap, long startTimeMs,
+ public OverlayFrame(MediaItem mediaItem, String overlayId, Bitmap bitmap, long startTimeMs,
long durationMs) {
- super(overlayId, startTimeMs, durationMs);
+ super(mediaItem, overlayId, startTimeMs, durationMs);
mBitmap = bitmap;
mFilename = null;
}
@@ -68,6 +69,7 @@ public class OverlayFrame extends Overlay { * Constructor for an OverlayFrame. This constructor can be used to
* restore the overlay after it was saved internally by the video editor.
*
+ * @param mediaItem The media item owner
* @param overlayId The overlay id
* @param filename The file name that contains the overlay.
* @param startTimeMs The overlay start time in milliseconds
@@ -76,8 +78,9 @@ public class OverlayFrame extends Overlay { * @throws IllegalArgumentException if the file type is not PNG or the
* startTimeMs and durationMs are incorrect.
*/
- OverlayFrame(String overlayId, String filename, long startTimeMs, long durationMs) {
- super(overlayId, startTimeMs, durationMs);
+ OverlayFrame(MediaItem mediaItem, String overlayId, String filename, long startTimeMs,
+ long durationMs) {
+ super(mediaItem, overlayId, startTimeMs, durationMs);
mFilename = filename;
mBitmap = BitmapFactory.decodeFile(mFilename);
}
@@ -90,6 +93,22 @@ public class OverlayFrame extends Overlay { }
/**
+ * @param bitmap The overlay bitmap
+ */
+ public void setBitmap(Bitmap bitmap) {
+ mBitmap = bitmap;
+ if (mFilename != null) {
+ // Delete the file
+ new File(mFilename).delete();
+ // Invalidate the filename
+ mFilename = null;
+ }
+
+ // Invalidate the transitions if necessary
+ getMediaItem().invalidateTransitions(this);
+ }
+
+ /**
* Get the file name of this overlay
*/
String getFilename() {
diff --git a/media/java/android/media/videoeditor/Transition.java b/media/java/android/media/videoeditor/Transition.java index e972aeb..1c82742 100755 --- a/media/java/android/media/videoeditor/Transition.java +++ b/media/java/android/media/videoeditor/Transition.java @@ -147,11 +147,12 @@ public abstract class Transition { */
public long getMaximumDuration() {
if (mAfterMediaItem == null) {
- return mBeforeMediaItem.getDuration() / 2;
+ return mBeforeMediaItem.getTimelineDuration() / 2;
} else if (mBeforeMediaItem == null) {
- return mAfterMediaItem.getDuration() / 2;
+ return mAfterMediaItem.getTimelineDuration() / 2;
} else {
- return (Math.min(mAfterMediaItem.getDuration(), mBeforeMediaItem.getDuration()) / 2);
+ return (Math.min(mAfterMediaItem.getTimelineDuration(),
+ mBeforeMediaItem.getTimelineDuration()) / 2);
}
}
diff --git a/media/java/android/media/videoeditor/VideoEditorTestImpl.java b/media/java/android/media/videoeditor/VideoEditorTestImpl.java index 7226d5d..5df4ea5 100644 --- a/media/java/android/media/videoeditor/VideoEditorTestImpl.java +++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java @@ -25,6 +25,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -50,6 +51,10 @@ public class VideoEditorTestImpl implements VideoEditor { private static final String TAG_MEDIA_ITEM = "media_item"; private static final String TAG_TRANSITIONS = "transitions"; private static final String TAG_TRANSITION = "transition"; + private static final String TAG_OVERLAYS = "overlays"; + private static final String TAG_OVERLAY = "overlay"; + private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes"; + private static final String ATTR_ID = "id"; private static final String ATTR_FILENAME = "filename"; private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "wavefoem"; @@ -568,7 +573,42 @@ public class VideoEditorTestImpl implements VideoEditor { mvi.getAudioWaveformFilename()); } } else if (mediaItem instanceof MediaImageItem) { - serializer.attribute("", ATTR_DURATION, Long.toString(mediaItem.getDuration())); + serializer.attribute("", ATTR_DURATION, + Long.toString(mediaItem.getTimelineDuration())); + } + + final List<Overlay> overlays = mediaItem.getAllOverlays(); + if (overlays.size() > 0) { + serializer.startTag("", TAG_OVERLAYS); + for (Overlay overlay : overlays) { + serializer.startTag("", TAG_OVERLAY); + serializer.attribute("", ATTR_ID, overlay.getId()); + serializer.attribute("", ATTR_TYPE, overlay.getClass().getSimpleName()); + serializer.attribute("", ATTR_BEGIN_TIME, + Long.toString(overlay.getStartTime())); + serializer.attribute("", ATTR_DURATION, Long.toString(overlay.getDuration())); + if (overlay instanceof OverlayFrame) { + final OverlayFrame overlayFrame = (OverlayFrame)overlay; + overlayFrame.save(this); + if (overlayFrame.getFilename() != null) { + serializer.attribute("", ATTR_FILENAME, overlayFrame.getFilename()); + } + } + + // Save the user attributes + serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES); + final Map<String, String> userAttributes = overlay.getUserAttributes(); + for (String name : userAttributes.keySet()) { + final String value = userAttributes.get(name); + if (value != null) { + serializer.attribute("", name, value); + } + } + serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES); + + serializer.endTag("", TAG_OVERLAY); + } + serializer.endTag("", TAG_OVERLAYS); } serializer.endTag("", TAG_MEDIA_ITEM); @@ -628,21 +668,22 @@ public class VideoEditorTestImpl implements VideoEditor { parser.setInput(new FileInputStream(file), "UTF-8"); int eventType = parser.getEventType(); String name; + MediaItem currentMediaItem = null; + Overlay currentOverlay = null; while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_TAG: { name = parser.getName(); - if (name.equals(TAG_PROJECT)) { + if (TAG_PROJECT.equals(name)) { mAspectRatio = Integer.parseInt(parser.getAttributeValue("", ATTR_ASPECT_RATIO)); - } else if (name.equals(TAG_MEDIA_ITEM)) { + } else if (TAG_MEDIA_ITEM.equals(name)) { final String mediaItemId = parser.getAttributeValue("", ATTR_ID); final String type = parser.getAttributeValue("", ATTR_TYPE); final String filename = parser.getAttributeValue("", ATTR_FILENAME); final int renderingMode = Integer.parseInt(parser.getAttributeValue("", ATTR_RENDERING_MODE)); - MediaItem currentMediaItem; if (MediaImageItem.class.getSimpleName().equals(type)) { final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); @@ -672,11 +713,36 @@ public class VideoEditorTestImpl implements VideoEditor { if (currentMediaItem != null) { mMediaItems.add(currentMediaItem); } - } else if (name.equals(TAG_TRANSITION)) { + } else if (TAG_TRANSITION.equals(name)) { final Transition transition = parseTransition(parser); if (transition != null) { mTransitions.add(transition); } + } else if (TAG_OVERLAY.equals(name)) { + if (currentMediaItem != null) { + currentOverlay = parseOverlay(parser, currentMediaItem); + if (currentOverlay != null) { + currentMediaItem.addOverlay(currentOverlay); + } + } + } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) { + if (currentOverlay != null) { + final int attributesCount = parser.getAttributeCount(); + for (int i = 0; i < attributesCount; i++) { + currentOverlay.setUserAttribute(parser.getAttributeName(i), + parser.getAttributeValue(i)); + } + } + } + break; + } + + case XmlPullParser.END_TAG: { + name = parser.getName(); + if (TAG_MEDIA_ITEM.equals(name)) { + currentMediaItem = null; + } else if (TAG_OVERLAY.equals(name)) { + currentOverlay = null; } break; } @@ -763,6 +829,31 @@ public class VideoEditorTestImpl implements VideoEditor { return transition; } + /** + * Parse the overlay + * + * @param parser The parser + * @param mediaItem The media item owner + * + * @return The overlay + */ + private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) { + final String overlayId = parser.getAttributeValue("", ATTR_ID); + final String type = parser.getAttributeValue("", ATTR_TYPE); + final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); + final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); + + final Overlay overlay; + if (OverlayFrame.class.getSimpleName().equals(type)) { + final String filename = parser.getAttributeValue("", ATTR_FILENAME); + overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs); + } else { + overlay = null; + } + + return overlay; + } + public void cancelExport(String filename) { } diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp index b5f4856..d6bf609 100644 --- a/media/jni/android_media_MtpDatabase.cpp +++ b/media/jni/android_media_MtpDatabase.cpp @@ -207,10 +207,13 @@ MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path, uint64_t size, time_t modified) { JNIEnv* env = AndroidRuntime::getJNIEnv(); + jstring pathStr = env->NewStringUTF(path); MtpObjectHandle result = env->CallIntMethod(mDatabase, method_beginSendObject, - env->NewStringUTF(path), (jint)format, (jint)parent, (jint)storage, + pathStr, (jint)format, (jint)parent, (jint)storage, (jlong)size, (jlong)modified); + if (pathStr) + env->DeleteLocalRef(pathStr); checkAndClearExceptionFromCallback(env, __FUNCTION__); return result; } @@ -218,9 +221,12 @@ MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path, void MyMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle, MtpObjectFormat format, bool succeeded) { JNIEnv* env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod(mDatabase, method_endSendObject, env->NewStringUTF(path), + jstring pathStr = env->NewStringUTF(path); + env->CallVoidMethod(mDatabase, method_endSendObject, pathStr, (jint)handle, (jint)format, (jboolean)succeeded); + if (pathStr) + env->DeleteLocalRef(pathStr); checkAndClearExceptionFromCallback(env, __FUNCTION__); } @@ -238,6 +244,7 @@ MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID, for (int i = 0; i < length; i++) list->push(handles[i]); env->ReleaseIntArrayElements(array, handles, 0); + env->DeleteLocalRef(array); checkAndClearExceptionFromCallback(env, __FUNCTION__); return list; @@ -266,6 +273,7 @@ MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() { for (int i = 0; i < length; i++) list->push(formats[i]); env->ReleaseIntArrayElements(array, formats, 0); + env->DeleteLocalRef(array); checkAndClearExceptionFromCallback(env, __FUNCTION__); return list; @@ -283,6 +291,7 @@ MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() { for (int i = 0; i < length; i++) list->push(formats[i]); env->ReleaseIntArrayElements(array, formats, 0); + env->DeleteLocalRef(array); checkAndClearExceptionFromCallback(env, __FUNCTION__); return list; @@ -300,6 +309,7 @@ MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectForm for (int i = 0; i < length; i++) list->push(properties[i]); env->ReleaseIntArrayElements(array, properties, 0); + env->DeleteLocalRef(array); checkAndClearExceptionFromCallback(env, __FUNCTION__); return list; @@ -317,6 +327,7 @@ MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() { for (int i = 0; i < length; i++) list->push(properties[i]); env->ReleaseIntArrayElements(array, properties, 0); + env->DeleteLocalRef(array); checkAndClearExceptionFromCallback(env, __FUNCTION__); return list; @@ -342,14 +353,21 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, jlong longValue = longValues[0]; env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); - // special case MTP_PROPERTY_DATE_MODIFIED, which is a string to MTP + // special case date properties, which are strings to MTP // but stored internally as a uint64 - if (property == MTP_PROPERTY_DATE_MODIFIED) { + if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) { char date[20]; formatDateTime(longValue, date, sizeof(date)); packet.putString(date); return MTP_RESPONSE_OK; } + // release date is stored internally as just the year + if (property == MTP_PROPERTY_ORIGINAL_RELEASE_DATE) { + char date[20]; + snprintf(date, sizeof(date), "%04lld0101T000000", longValue); + packet.putString(date); + return MTP_RESPONSE_OK; + } switch (type) { case MTP_TYPE_INT8: @@ -449,6 +467,8 @@ MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle, jint result = env->CallIntMethod(mDatabase, method_setObjectProperty, (jint)handle, (jint)property, longValue, stringValue); + if (stringValue) + env->DeleteLocalRef(stringValue); checkAndClearExceptionFromCallback(env, __FUNCTION__); return result; @@ -570,6 +590,8 @@ MtpResponseCode MyMtpDatabase::setDevicePropertyValue(MtpDeviceProperty property jint result = env->CallIntMethod(mDatabase, method_setDeviceProperty, (jint)property, longValue, stringValue); + if (stringValue) + env->DeleteLocalRef(stringValue); checkAndClearExceptionFromCallback(env, __FUNCTION__); return result; @@ -680,6 +702,17 @@ static const PropertyTableEntry kObjectPropertyTable[] = { { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 }, { MTP_PROPERTY_PERSISTENT_UID, MTP_TYPE_UINT128 }, { MTP_PROPERTY_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_DISPLAY_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_DATE_ADDED, MTP_TYPE_STR }, + { MTP_PROPERTY_ARTIST, MTP_TYPE_STR }, + { MTP_PROPERTY_ALBUM_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_ALBUM_ARTIST, MTP_TYPE_STR }, + { MTP_PROPERTY_TRACK, MTP_TYPE_UINT16 }, + { MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_TYPE_STR }, + { MTP_PROPERTY_GENRE, MTP_TYPE_STR }, + { MTP_PROPERTY_COMPOSER, MTP_TYPE_STR }, + { MTP_PROPERTY_DURATION, MTP_TYPE_UINT32 }, + { MTP_PROPERTY_DESCRIPTION, MTP_TYPE_STR }, }; static const PropertyTableEntry kDevicePropertyTable[] = { @@ -723,6 +756,7 @@ MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle) for (int i = 0; i < length; i++) list->push(handles[i]); env->ReleaseIntArrayElements(array, handles, 0); + env->DeleteLocalRef(array); checkAndClearExceptionFromCallback(env, __FUNCTION__); return list; @@ -743,6 +777,7 @@ MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle, env->ReleaseIntArrayElements(array, handles, 0); MtpResponseCode result = env->CallIntMethod(mDatabase, method_setObjectReferences, (jint)handle, array); + env->DeleteLocalRef(array); checkAndClearExceptionFromCallback(env, __FUNCTION__); return result; @@ -754,10 +789,12 @@ MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property, switch (property) { case MTP_PROPERTY_OBJECT_FORMAT: case MTP_PROPERTY_PROTECTION_STATUS: + case MTP_PROPERTY_TRACK: result = new MtpProperty(property, MTP_TYPE_UINT16); break; case MTP_PROPERTY_STORAGE_ID: case MTP_PROPERTY_PARENT_OBJECT: + case MTP_PROPERTY_DURATION: result = new MtpProperty(property, MTP_TYPE_UINT32); break; case MTP_PROPERTY_OBJECT_SIZE: @@ -769,6 +806,15 @@ MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property, case MTP_PROPERTY_NAME: case MTP_PROPERTY_OBJECT_FILE_NAME: case MTP_PROPERTY_DATE_MODIFIED: + case MTP_PROPERTY_DISPLAY_NAME: + case MTP_PROPERTY_DATE_ADDED: + case MTP_PROPERTY_ARTIST: + case MTP_PROPERTY_ALBUM_NAME: + case MTP_PROPERTY_ALBUM_ARTIST: + case MTP_PROPERTY_ORIGINAL_RELEASE_DATE: + case MTP_PROPERTY_GENRE: + case MTP_PROPERTY_COMPOSER: + case MTP_PROPERTY_DESCRIPTION: result = new MtpProperty(property, MTP_TYPE_STR); break; } diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp index 8f40130..ebe3302 100644 --- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp +++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp @@ -63,7 +63,6 @@ namespace { // Flag to allow a one time init of global memory, only happens on first call ever int LvmInitFlag = LVM_FALSE; -int LvmSessionsActive = 0; SessionContext GlobalSessionMemory[LVM_MAX_SESSIONS]; int SessionIndex[LVM_MAX_SESSIONS]; @@ -189,16 +188,19 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, int32_t sessionId, int32_t ioId, effect_interface_t *pInterface){ - int ret; + int ret = 0; int sessionNo; int i; - EffectContext *pContext = new EffectContext; + EffectContext *pContext = NULL; + bool newBundle = false; + SessionContext *pSessionContext; LOGV("\n\tEffectCreate start session %d", sessionId); if (pInterface == NULL || uuid == NULL){ LOGV("\tLVM_ERROR : EffectCreate() called with NULL pointer"); - return -EINVAL; + ret = -EINVAL; + goto exit; } if(LvmInitFlag == LVM_FALSE){ @@ -207,8 +209,6 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, LvmGlobalBundle_init(); } - LOGV("\tEffectCreate: There are %d LVM sessions acive\n", LvmSessionsActive); - // Find next available sessionNo for(i=0; i<LVM_MAX_SESSIONS; i++){ if((SessionIndex[i] == LVM_UNUSED_SESSION)||(SessionIndex[i] == sessionId)){ @@ -221,23 +221,20 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, if(i==LVM_MAX_SESSIONS){ LOGV("\tLVM_ERROR : Cannot find memory to allocate for current session"); - return -EINVAL; + ret = -EINVAL; + goto exit; } + + pContext = new EffectContext; + // If this is the first create in this session if(GlobalSessionMemory[sessionNo].bBundledEffectsEnabled == LVM_FALSE){ LOGV("\tEffectCreate - This is the first effect in current sessionId %d sessionNo %d", sessionId, sessionNo); - LvmSessionsActive++; - - if(LvmSessionsActive >= LVM_MAX_SESSIONS){ - LOGV("\tLVM_ERROR : Number of active session is greater than LVM_MAX_SESSIONS (%d)", - LVM_MAX_SESSIONS); - return -EINVAL; - } - GlobalSessionMemory[sessionNo].bBundledEffectsEnabled = LVM_TRUE; GlobalSessionMemory[sessionNo].pBundledContext = new BundledEffectContext; + newBundle = true; pContext->pBundledContext = GlobalSessionMemory[sessionNo].pBundledContext; pContext->pBundledContext->SessionNo = sessionNo; @@ -251,17 +248,16 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, pContext->pBundledContext->bVirtualizerTempDisabled = LVM_FALSE; pContext->pBundledContext->NumberEffectsEnabled = 0; pContext->pBundledContext->NumberEffectsCalled = 0; - pContext->pBundledContext->frameCount = 0; pContext->pBundledContext->firstVolume = LVM_TRUE; #ifdef LVM_PCM - char fileName[256]; snprintf(fileName, 256, "/data/tmp/bundle_%p_pcm_in.pcm", pContext->pBundledContext); pContext->pBundledContext->PcmInPtr = fopen(fileName, "w"); if (pContext->pBundledContext->PcmInPtr == NULL) { LOGV("cannot open %s", fileName); - return -EINVAL; + ret = -EINVAL; + goto exit; } snprintf(fileName, 256, "/data/tmp/bundle_%p_pcm_out.pcm", pContext->pBundledContext); @@ -270,7 +266,8 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, LOGV("cannot open %s", fileName); fclose(pContext->pBundledContext->PcmInPtr); pContext->pBundledContext->PcmInPtr = NULL; - return -EINVAL; + ret = -EINVAL; + goto exit; } #endif @@ -285,15 +282,18 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, pContext->pBundledContext->bMuteEnabled = LVM_FALSE; pContext->pBundledContext->bStereoPositionEnabled = LVM_FALSE; pContext->pBundledContext->positionSaved = 0; + pContext->pBundledContext->workBuffer = NULL; + pContext->pBundledContext->frameCount = -1; + pContext->pBundledContext->SamplesToExitCountVirt = 0; + pContext->pBundledContext->SamplesToExitCountBb = 0; + pContext->pBundledContext->SamplesToExitCountEq = 0; LOGV("\tEffectCreate - Calling LvmBundle_init"); ret = LvmBundle_init(pContext); if (ret < 0){ LOGV("\tLVM_ERROR : EffectCreate() Bundle init failed"); - delete pContext->pBundledContext; - delete pContext; - return ret; + goto exit; } } else{ @@ -304,13 +304,14 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, } LOGV("\tEffectCreate - pBundledContext is %p", pContext->pBundledContext); - SessionContext *pSessionContext = &GlobalSessionMemory[pContext->pBundledContext->SessionNo]; + pSessionContext = &GlobalSessionMemory[pContext->pBundledContext->SessionNo]; // Create each Effect if (memcmp(uuid, &gBassBoostDescriptor.uuid, sizeof(effect_uuid_t)) == 0){ // Create Bass Boost LOGV("\tEffectCreate - Effect to be created is LVM_BASS_BOOST"); pSessionContext->bBassInstantiated = LVM_TRUE; + pContext->pBundledContext->SamplesToExitCountBb = 0; pContext->itfe = &gLvmEffectInterface; pContext->EffectType = LVM_BASS_BOOST; @@ -318,6 +319,7 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, // Create Virtualizer LOGV("\tEffectCreate - Effect to be created is LVM_VIRTUALIZER"); pSessionContext->bVirtualizerInstantiated=LVM_TRUE; + pContext->pBundledContext->SamplesToExitCountVirt = 0; pContext->itfe = &gLvmEffectInterface; pContext->EffectType = LVM_VIRTUALIZER; @@ -325,6 +327,7 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, // Create Equalizer LOGV("\tEffectCreate - Effect to be created is LVM_EQUALIZER"); pSessionContext->bEqualizerInstantiated = LVM_TRUE; + pContext->pBundledContext->SamplesToExitCountEq = 0; pContext->itfe = &gLvmEffectInterface; pContext->EffectType = LVM_EQUALIZER; @@ -338,46 +341,77 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, } else{ LOGV("\tLVM_ERROR : EffectCreate() invalid UUID"); - return -EINVAL; + ret = -EINVAL; + goto exit; } - *pInterface = (effect_interface_t)pContext; +exit: + if (ret != 0) { + if (pContext != NULL) { + if (newBundle) { + GlobalSessionMemory[sessionNo].bBundledEffectsEnabled = LVM_FALSE; + SessionIndex[sessionNo] = LVM_UNUSED_SESSION; + delete pContext->pBundledContext; + } + delete pContext; + } + *pInterface = (effect_interface_t)NULL; + } else { + *pInterface = (effect_interface_t)pContext; + } LOGV("\tEffectCreate end..\n\n"); - return 0; + return ret; } /* end EffectCreate */ extern "C" int EffectRelease(effect_interface_t interface){ LOGV("\n\tEffectRelease start %p", interface); EffectContext * pContext = (EffectContext *)interface; - LOGV("\n\tEffectRelease start interface: %p, context %p", interface, pContext->pBundledContext); + LOGV("\tEffectRelease start interface: %p, context %p", interface, pContext->pBundledContext); if (pContext == NULL){ LOGV("\tLVM_ERROR : EffectRelease called with NULL pointer"); return -EINVAL; } - - Effect_setEnabled(pContext, LVM_FALSE); - SessionContext *pSessionContext = &GlobalSessionMemory[pContext->pBundledContext->SessionNo]; // Clear the instantiated flag for the effect + // protect agains the case where an effect is un-instantiated without being disabled if(pContext->EffectType == LVM_BASS_BOOST) { LOGV("\tEffectRelease LVM_BASS_BOOST Clearing global intstantiated flag"); pSessionContext->bBassInstantiated = LVM_FALSE; + if(pContext->pBundledContext->SamplesToExitCountBb > 0){ + pContext->pBundledContext->NumberEffectsEnabled--; + } + pContext->pBundledContext->SamplesToExitCountBb = 0; } else if(pContext->EffectType == LVM_VIRTUALIZER) { LOGV("\tEffectRelease LVM_VIRTUALIZER Clearing global intstantiated flag"); pSessionContext->bVirtualizerInstantiated = LVM_FALSE; + if(pContext->pBundledContext->SamplesToExitCountVirt > 0){ + pContext->pBundledContext->NumberEffectsEnabled--; + } + pContext->pBundledContext->SamplesToExitCountVirt = 0; } else if(pContext->EffectType == LVM_EQUALIZER) { LOGV("\tEffectRelease LVM_EQUALIZER Clearing global intstantiated flag"); pSessionContext->bEqualizerInstantiated =LVM_FALSE; + if(pContext->pBundledContext->SamplesToExitCountEq > 0){ + pContext->pBundledContext->NumberEffectsEnabled--; + } + pContext->pBundledContext->SamplesToExitCountEq = 0; } else if(pContext->EffectType == LVM_VOLUME) { LOGV("\tEffectRelease LVM_VOLUME Clearing global intstantiated flag"); pSessionContext->bVolumeInstantiated = LVM_FALSE; + if (pContext->pBundledContext->bVolumeEnabled == LVM_TRUE){ + pContext->pBundledContext->NumberEffectsEnabled--; + } } else { LOGV("\tLVM_ERROR : EffectRelease : Unsupported effect\n\n\n\n\n\n\n"); } + // Disable effect, in this case ignore errors (return codes) + // if an effect has already been disabled + Effect_setEnabled(pContext, LVM_FALSE); + // if all effects are no longer instantiaed free the lvm memory and delete BundledEffectContext if ((pSessionContext->bBassInstantiated == LVM_FALSE) && (pSessionContext->bVolumeInstantiated == LVM_FALSE) && @@ -395,8 +429,6 @@ extern "C" int EffectRelease(effect_interface_t interface){ } #endif - LvmSessionsActive--; - LOGV("\tEffectRelease: There are %d LVM sessions remaining\n", LvmSessionsActive); // Clear the SessionIndex for(int i=0; i<LVM_MAX_SESSIONS; i++){ @@ -409,11 +441,14 @@ extern "C" int EffectRelease(effect_interface_t interface){ } LOGV("\tEffectRelease: All effects are no longer instantiated\n"); - pSessionContext->bBundledEffectsEnabled =LVM_FALSE; + pSessionContext->bBundledEffectsEnabled = LVM_FALSE; pSessionContext->pBundledContext = LVM_NULL; LOGV("\tEffectRelease: Freeing LVM Bundle memory\n"); LvmEffect_free(pContext); LOGV("\tEffectRelease: Deleting LVM Bundle context %p\n", pContext->pBundledContext); + if (pContext->pBundledContext->workBuffer != NULL) { + free(pContext->pBundledContext->workBuffer); + } delete pContext->pBundledContext; pContext->pBundledContext = LVM_NULL; } @@ -643,6 +678,14 @@ int LvmBundle_init(EffectContext *pContext){ return 0; } /* end LvmBundle_init */ + +static inline int16_t clamp16(int32_t sample) +{ + if ((sample>>15) ^ (sample>>31)) + sample = 0x7FFF ^ (sample>>31); + return sample; +} + //---------------------------------------------------------------------------- // LvmBundle_process() //---------------------------------------------------------------------------- @@ -668,39 +711,25 @@ int LvmBundle_process(LVM_INT16 *pIn, LVM_ControlParams_t ActiveParams; /* Current control Parameters */ LVM_ReturnStatus_en LvmStatus = LVM_SUCCESS; /* Function call status */ - LVM_INT16 *pOutTmp; + if (pContext->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_WRITE){ pOutTmp = pOut; }else if (pContext->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE){ - pOutTmp = (LVM_INT16 *)malloc(frameCount * sizeof(LVM_INT16) * 2); - if(pOutTmp == NULL){ - LOGV("\tLVM_ERROR : LvmBundle_process failed to allocate memory for " - "EFFECT_BUFFER_ACCESS_ACCUMULATE mode"); - return -EINVAL; + if (pContext->pBundledContext->frameCount != frameCount) { + if (pContext->pBundledContext->workBuffer != NULL) { + free(pContext->pBundledContext->workBuffer); + } + pContext->pBundledContext->workBuffer = + (LVM_INT16 *)malloc(frameCount * sizeof(LVM_INT16) * 2); + pContext->pBundledContext->frameCount = frameCount; } + pOutTmp = pContext->pBundledContext->workBuffer; }else{ LOGV("LVM_ERROR : LvmBundle_process invalid access mode"); return -EINVAL; } - /* Get the current settings */ - LvmStatus = LVM_GetControlParameters(pContext->pBundledContext->hInstance, - &ActiveParams); - - LVM_ERROR_CHECK(LvmStatus, "LVM_GetControlParameters", "LvmBundle_process") - if(LvmStatus != LVM_SUCCESS) return -EINVAL; - - pContext->pBundledContext->frameCount++; - if(pContext->pBundledContext->frameCount == 100) - { - //LOGV("\tBB: %d VIRT: %d EQ: %d, session (%d), context is %p\n", - //ActiveParams.BE_OperatingMode, - //ActiveParams.VirtualizerOperatingMode, ActiveParams.EQNB_OperatingMode, - //pContext->pBundledContext->SessionNo, pContext->pBundledContext); - pContext->pBundledContext->frameCount = 0; - } - #ifdef LVM_PCM fwrite(pIn, frameCount*sizeof(LVM_INT16)*2, 1, pContext->pBundledContext->PcmInPtr); fflush(pContext->pBundledContext->PcmInPtr); @@ -725,9 +754,8 @@ int LvmBundle_process(LVM_INT16 *pIn, if (pContext->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE){ for (int i=0; i<frameCount*2; i++){ - pOut[i] += pOutTmp[i]; + pOut[i] = clamp16((LVM_INT32)pOut[i] + (LVM_INT32)pOutTmp[i]); } - free(pOutTmp); } return 0; } /* end LvmBundle_process */ @@ -813,15 +841,15 @@ int LvmEffect_disable(EffectContext *pContext){ ActiveParams.BE_OperatingMode = LVM_BE_OFF; } if(pContext->EffectType == LVM_VIRTUALIZER) { - LOGV("\tLvmEffect_disable : Enabling LVM_VIRTUALIZER"); + LOGV("\tLvmEffect_disable : Disabling LVM_VIRTUALIZER"); ActiveParams.VirtualizerOperatingMode = LVM_MODE_OFF; } if(pContext->EffectType == LVM_EQUALIZER) { - LOGV("\tLvmEffect_disable : Enabling LVM_EQUALIZER"); + LOGV("\tLvmEffect_disable : Disabling LVM_EQUALIZER"); ActiveParams.EQNB_OperatingMode = LVM_EQNB_OFF; } if(pContext->EffectType == LVM_VOLUME) { - LOGV("\tLvmEffect_disable : Enabling LVM_VOLUME"); + LOGV("\tLvmEffect_disable : Disabling LVM_VOLUME"); } LvmStatus = LVM_SetControlParameters(pContext->pBundledContext->hInstance, &ActiveParams); @@ -2406,85 +2434,114 @@ int Effect_setEnabled(EffectContext *pContext, bool enabled) switch (pContext->EffectType) { case LVM_BASS_BOOST: if (pContext->pBundledContext->bBassEnabled == LVM_TRUE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_BASS_BOOST is already enabled"); + LOGV("\tEffect_setEnabled() LVM_BASS_BOOST is already enabled"); return -EINVAL; } + if(pContext->pBundledContext->SamplesToExitCountBb <= 0){ + pContext->pBundledContext->NumberEffectsEnabled++; + } pContext->pBundledContext->SamplesToExitCountBb = (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1); pContext->pBundledContext->bBassEnabled = LVM_TRUE; break; case LVM_EQUALIZER: if (pContext->pBundledContext->bEqualizerEnabled == LVM_TRUE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_EQUALIZER is already enabled"); + LOGV("\tEffect_setEnabled() LVM_EQUALIZER is already enabled"); return -EINVAL; } + if(pContext->pBundledContext->SamplesToExitCountEq <= 0){ + pContext->pBundledContext->NumberEffectsEnabled++; + } pContext->pBundledContext->SamplesToExitCountEq = (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1); pContext->pBundledContext->bEqualizerEnabled = LVM_TRUE; break; case LVM_VIRTUALIZER: if (pContext->pBundledContext->bVirtualizerEnabled == LVM_TRUE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VIRTUALIZER is already enabled"); + LOGV("\tEffect_setEnabled() LVM_VIRTUALIZER is already enabled"); return -EINVAL; } + if(pContext->pBundledContext->SamplesToExitCountVirt <= 0){ + pContext->pBundledContext->NumberEffectsEnabled++; + } pContext->pBundledContext->SamplesToExitCountVirt = (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1); pContext->pBundledContext->bVirtualizerEnabled = LVM_TRUE; break; case LVM_VOLUME: if (pContext->pBundledContext->bVolumeEnabled == LVM_TRUE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VOLUME is already enabled"); + LOGV("\tEffect_setEnabled() LVM_VOLUME is already enabled"); return -EINVAL; } + pContext->pBundledContext->NumberEffectsEnabled++; pContext->pBundledContext->bVolumeEnabled = LVM_TRUE; break; default: - LOGV("\tLVM_ERROR : Effect_setEnabled() invalid effect type"); + LOGV("\tEffect_setEnabled() invalid effect type"); return -EINVAL; } - pContext->pBundledContext->NumberEffectsEnabled++; LvmEffect_enable(pContext); } else { switch (pContext->EffectType) { case LVM_BASS_BOOST: if (pContext->pBundledContext->bBassEnabled == LVM_FALSE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_BASS_BOOST is already disabled"); + LOGV("\tEffect_setEnabled() LVM_BASS_BOOST is already disabled"); return -EINVAL; } pContext->pBundledContext->bBassEnabled = LVM_FALSE; break; case LVM_EQUALIZER: if (pContext->pBundledContext->bEqualizerEnabled == LVM_FALSE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_EQUALIZER is already disabled"); + LOGV("\tEffect_setEnabled() LVM_EQUALIZER is already disabled"); return -EINVAL; } pContext->pBundledContext->bEqualizerEnabled = LVM_FALSE; break; case LVM_VIRTUALIZER: if (pContext->pBundledContext->bVirtualizerEnabled == LVM_FALSE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VIRTUALIZER is already disabled"); + LOGV("\tEffect_setEnabled() LVM_VIRTUALIZER is already disabled"); return -EINVAL; } pContext->pBundledContext->bVirtualizerEnabled = LVM_FALSE; break; case LVM_VOLUME: if (pContext->pBundledContext->bVolumeEnabled == LVM_FALSE) { - LOGV("\tLVM_ERROR : Effect_setEnabled() LVM_VOLUME is already disabled"); + LOGV("\tEffect_setEnabled() LVM_VOLUME is already disabled"); return -EINVAL; } pContext->pBundledContext->bVolumeEnabled = LVM_FALSE; break; default: - LOGV("\tLVM_ERROR : Effect_setEnabled() invalid effect type"); + LOGV("\tEffect_setEnabled() invalid effect type"); return -EINVAL; } - pContext->pBundledContext->NumberEffectsEnabled--; LvmEffect_disable(pContext); } return 0; } +//---------------------------------------------------------------------------- +// LVC_Convert_VolToDb() +//---------------------------------------------------------------------------- +// Purpose: +// Convery volume in Q24 to dB +// +// Inputs: +// vol: Q.24 volume dB +// +//----------------------------------------------------------------------- + +int16_t LVC_Convert_VolToDb(uint32_t vol){ + int16_t dB; + + dB = LVC_ToDB_s32Tos16(vol <<7); + dB = (dB +8)>>4; + dB = (dB <-96) ? -96 : dB ; + + return dB; +} + } // namespace } // namespace @@ -2493,32 +2550,31 @@ extern "C" int Effect_process(effect_interface_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer){ EffectContext * pContext = (EffectContext *) self; - LVM_ControlParams_t ActiveParams; /* Current control Parameters */ LVM_ReturnStatus_en LvmStatus = LVM_SUCCESS; /* Function call status */ int status = 0; - int status2Sec = 0; int lvmStatus = 0; LVM_INT16 *in = (LVM_INT16 *)inBuffer->raw; LVM_INT16 *out = (LVM_INT16 *)outBuffer->raw; -//LOGV("\tEffect_process Start : Enabled = %d Called = %d", -//pContext->pBundledContext->NumberEffectsEnabled,pContext->pBundledContext->NumberEffectsCalled); -// LOGV("\tEffect_process Start : Samples left %d %d %d", +//LOGV("\tEffect_process Start : Enabled = %d Called = %d (%8d %8d %8d)", +//pContext->pBundledContext->NumberEffectsEnabled,pContext->pBundledContext->NumberEffectsCalled, // pContext->pBundledContext->SamplesToExitCountBb, // pContext->pBundledContext->SamplesToExitCountVirt, // pContext->pBundledContext->SamplesToExitCountEq); -// LvmStatus = LVM_GetControlParameters(pContext->pBundledContext->hInstance, &ActiveParams); -// LVM_ERROR_CHECK(LvmStatus, "LVM_GetControlParameters", "VolumeGetStereoPosition") -// if(LvmStatus != LVM_SUCCESS) return -EINVAL; -// LOGV("\tEffect_process Internal Operating Modes: BB %d VIRT %d EQ %d", -// ActiveParams.BE_OperatingMode, ActiveParams.VirtualizerOperatingMode, -// ActiveParams.EQNB_OperatingMode); - if (pContext == NULL){ LOGV("\tLVM_ERROR : Effect_process() ERROR pContext == NULL"); return -EINVAL; } + + //if(pContext->EffectType == LVM_BASS_BOOST){ + // LOGV("\tEffect_process: Effect type is BASS_BOOST"); + //}else if(pContext->EffectType == LVM_EQUALIZER){ + // LOGV("\tEffect_process: Effect type is LVM_EQUALIZER"); + //}else if(pContext->EffectType == LVM_VIRTUALIZER){ + // LOGV("\tEffect_process: Effect type is LVM_VIRTUALIZER"); + //} + if (inBuffer == NULL || inBuffer->raw == NULL || outBuffer == NULL || outBuffer->raw == NULL || inBuffer->frameCount != outBuffer->frameCount){ @@ -2529,70 +2585,57 @@ extern "C" int Effect_process(effect_interface_t self, (pContext->EffectType == LVM_BASS_BOOST)){ //LOGV("\tEffect_process() LVM_BASS_BOOST Effect is not enabled"); if(pContext->pBundledContext->SamplesToExitCountBb > 0){ - status2Sec = -ENODATA; pContext->pBundledContext->SamplesToExitCountBb -= outBuffer->frameCount * 2; // STEREO //LOGV("\tEffect_process: Waiting to turn off BASS_BOOST, %d samples left", // pContext->pBundledContext->SamplesToExitCountBb); } else { status = -ENODATA; + pContext->pBundledContext->NumberEffectsEnabled--; } } if ((pContext->pBundledContext->bVolumeEnabled == LVM_FALSE)&& (pContext->EffectType == LVM_VOLUME)){ //LOGV("\tEffect_process() LVM_VOLUME Effect is not enabled"); status = -ENODATA; + pContext->pBundledContext->NumberEffectsEnabled--; } if ((pContext->pBundledContext->bEqualizerEnabled == LVM_FALSE)&& (pContext->EffectType == LVM_EQUALIZER)){ //LOGV("\tEffect_process() LVM_EQUALIZER Effect is not enabled"); if(pContext->pBundledContext->SamplesToExitCountEq > 0){ - status2Sec = -ENODATA; pContext->pBundledContext->SamplesToExitCountEq -= outBuffer->frameCount * 2; // STEREO - //LOGV("\tEffect_process: Waiting for 2 secs to turn off EQUALIZER, %d samples left", + //LOGV("\tEffect_process: Waiting to turn off EQUALIZER, %d samples left", // pContext->pBundledContext->SamplesToExitCountEq); } else { status = -ENODATA; + pContext->pBundledContext->NumberEffectsEnabled--; } } if ((pContext->pBundledContext->bVirtualizerEnabled == LVM_FALSE)&& (pContext->EffectType == LVM_VIRTUALIZER)){ //LOGV("\tEffect_process() LVM_VIRTUALIZER Effect is not enabled"); if(pContext->pBundledContext->SamplesToExitCountVirt > 0){ - status2Sec = -ENODATA; pContext->pBundledContext->SamplesToExitCountVirt -= outBuffer->frameCount * 2;// STEREO - //LOGV("\tEffect_process: Waiting for 2 secs to turn off VIRTUALIZER, %d samples left", + //LOGV("\tEffect_process: Waiting for to turn off VIRTUALIZER, %d samples left", // pContext->pBundledContext->SamplesToExitCountVirt); } else { status = -ENODATA; + pContext->pBundledContext->NumberEffectsEnabled--; } } - // If this is the last frame of an effect process its output with no effect - if(status == -ENODATA){ - if (pContext->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE){ - //LOGV("\tLVM_ERROR : Effect_process() accumulating last frame into output buffer"); - //LOGV("\tLVM_ERROR : Effect_process() trying copying last frame into output buffer"); - //LOGV("\tLVM_ERROR : Enabled = %d Called = %d", - //pContext->pBundledContext->NumberEffectsEnabled, - //pContext->pBundledContext->NumberEffectsCalled); - - }else{ - //LOGV("\tLVM_ERROR : Effect_process() copying last frame into output buffer"); - } - } - - if((status2Sec != -ENODATA)&&(status != -ENODATA)){ + if(status != -ENODATA){ pContext->pBundledContext->NumberEffectsCalled++; } if(pContext->pBundledContext->NumberEffectsCalled == pContext->pBundledContext->NumberEffectsEnabled){ - //LOGV("\tEffect_process Calling process with %d effects enabled, %d called: Effect %d", + //LOGV("\tEffect_process Calling process with %d effects enabled, %d called: Effect %d", //pContext->pBundledContext->NumberEffectsEnabled, //pContext->pBundledContext->NumberEffectsCalled, pContext->EffectType); if(status == -ENODATA){ - //LOGV("\tLVM_ERROR : Effect_process() actually processing last frame"); + LOGV("\tEffect_process() processing last frame"); } pContext->pBundledContext->NumberEffectsCalled = 0; /* Process all the available frames, block processing is @@ -2836,10 +2879,10 @@ extern "C" int Effect_command(effect_interface_t self, case EFFECT_CMD_SET_PARAM:{ //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_SET_PARAM start"); if(pContext->EffectType == LVM_BASS_BOOST){ - //LOGV("\tBassBoost_command EFFECT_CMD_SET_PARAM param %d, *replySize %d, value %d ", - // *(int32_t *)((char *)pCmdData + sizeof(effect_param_t)), - // *replySize, - // *(int16_t *)((char *)pCmdData + sizeof(effect_param_t) + sizeof(int32_t))); + //LOGV("\tBassBoost_command EFFECT_CMD_SET_PARAM param %d, *replySize %d, value %d", + // *(int32_t *)((char *)pCmdData + sizeof(effect_param_t)), + // *replySize, + // *(int16_t *)((char *)pCmdData + sizeof(effect_param_t) + sizeof(int32_t))); if (pCmdData == NULL|| cmdSize != (int)(sizeof(effect_param_t) + sizeof(int32_t) +sizeof(int16_t))|| @@ -3038,30 +3081,71 @@ extern "C" int Effect_command(effect_interface_t self, } case EFFECT_CMD_SET_VOLUME: { - int32_t vol = *(int32_t *)pCmdData; - int16_t dB; - int32_t vol_ret[2] = {1<<24,1<<24}; // Apply no volume + uint32_t leftVolume, rightVolume; + int16_t leftdB, rightdB; + int16_t maxdB, pandB; + int32_t vol_ret[2] = {1<<24,1<<24}; // Apply no volume + int status = 0; + LVM_ControlParams_t ActiveParams; /* Current control Parameters */ + LVM_ReturnStatus_en LvmStatus=LVM_SUCCESS; /* Function call status */ // if pReplyData is NULL, VOL_CTRL is delegated to another effect if(pReplyData == LVM_NULL){ break; } - if(vol==0x1000000){ - vol -= 1; + if (pCmdData == NULL || + cmdSize != 2 * sizeof(uint32_t)) { + LOGV("\tLVM_ERROR : Effect_command cmdCode Case: " + "EFFECT_CMD_SET_VOLUME: ERROR"); + return -EINVAL; } - // Convert volume linear (Q8.24) to volume dB (0->-96) - dB = android::LVC_ToDB_s32Tos16(vol <<7); - dB = (dB +8)>>4; - dB = (dB <-96) ? -96 : dB ; - LOGV("\tEFFECT_CMD_SET_VOLUME Session: %d, SessionID: %d VOLUME is %d dB (%d), " - "effect is %d", - pContext->pBundledContext->SessionNo, pContext->pBundledContext->SessionId, - (int32_t)dB, vol<<7, pContext->EffectType); + leftVolume = ((*(uint32_t *)pCmdData)); + rightVolume = ((*((uint32_t *)pCmdData + 1))); + + if(leftVolume == 0x1000000){ + leftVolume -= 1; + } + if(rightVolume == 0x1000000){ + rightVolume -= 1; + } + + // Convert volume to dB + leftdB = android::LVC_Convert_VolToDb(leftVolume); + rightdB = android::LVC_Convert_VolToDb(rightVolume); + + pandB = rightdB - leftdB; + + // Calculate max volume in dB + maxdB = leftdB; + if(rightdB > maxdB){ + maxdB = rightdB; + } + //LOGV("\tEFFECT_CMD_SET_VOLUME Session: %d, SessionID: %d VOLUME is %d dB (%d), " + // "effect is %d", + //pContext->pBundledContext->SessionNo, pContext->pBundledContext->SessionId, + //(int32_t)maxdB, maxVol<<7, pContext->EffectType); + //LOGV("\tEFFECT_CMD_SET_VOLUME: Left is %d, Right is %d", leftVolume, rightVolume); + //LOGV("\tEFFECT_CMD_SET_VOLUME: Left %ddB, Right %ddB, Position %ddB", + // leftdB, rightdB, pandB); memcpy(pReplyData, vol_ret, sizeof(int32_t)*2); - android::VolumeSetVolumeLevel(pContext, (int16_t)(dB*100)); + android::VolumeSetVolumeLevel(pContext, (int16_t)(maxdB*100)); + + /* Get the current settings */ + LvmStatus =LVM_GetControlParameters(pContext->pBundledContext->hInstance,&ActiveParams); + LVM_ERROR_CHECK(LvmStatus, "LVM_GetControlParameters", "VolumeSetStereoPosition") + if(LvmStatus != LVM_SUCCESS) return -EINVAL; + + /* Volume parameters */ + ActiveParams.VC_Balance = pandB; + LOGV("\t\tVolumeSetStereoPosition() (-96dB -> +96dB)-> %d\n", ActiveParams.VC_Balance ); + + /* Activate the initial settings */ + LvmStatus =LVM_SetControlParameters(pContext->pBundledContext->hInstance,&ActiveParams); + LVM_ERROR_CHECK(LvmStatus, "LVM_SetControlParameters", "VolumeSetStereoPosition") + if(LvmStatus != LVM_SUCCESS) return -EINVAL; break; } case EFFECT_CMD_SET_AUDIO_MODE: diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h index 91963af..2b51029 100644 --- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h +++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h @@ -88,12 +88,13 @@ struct BundledEffectContext{ int positionSaved; bool bMuteEnabled; /* Must store as mute = -96dB level */ bool bStereoPositionEnabled; - int frameCount; LVM_Fs_en SampleRate; int SamplesPerSecond; int SamplesToExitCountEq; int SamplesToExitCountBb; int SamplesToExitCountVirt; + LVM_INT16 *workBuffer; + int frameCount; #ifdef LVM_PCM FILE *PcmInPtr; FILE *PcmOutPtr; diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp index 03a6bbb..5505f14 100644 --- a/media/libeffects/visualizer/EffectVisualizer.cpp +++ b/media/libeffects/visualizer/EffectVisualizer.cpp @@ -65,8 +65,8 @@ void Visualizer_reset(VisualizerContext *pContext) { pContext->mCaptureIdx = 0; pContext->mCurrentBuf = 0; - memset(pContext->mCaptureBuf[0], 0, VISUALIZER_CAPTURE_SIZE_MAX); - memset(pContext->mCaptureBuf[1], 0, VISUALIZER_CAPTURE_SIZE_MAX); + memset(pContext->mCaptureBuf[0], 0x80, VISUALIZER_CAPTURE_SIZE_MAX); + memset(pContext->mCaptureBuf[1], 0x80, VISUALIZER_CAPTURE_SIZE_MAX); } //---------------------------------------------------------------------------- diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index 0f3e245..88b8c86 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -228,24 +228,32 @@ status_t AudioEffect::command(uint32_t cmdCode, void *replyData) { if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) { + LOGV("command() bad status %d", mStatus); return INVALID_OPERATION; } - status_t status = mIEffect->command(cmdCode, cmdSize, cmdData, replySize, replyData); - if (status != NO_ERROR) { - return status; + if ((cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) && + (replySize == NULL || *replySize != sizeof(status_t) || replyData == NULL)) { + return BAD_VALUE; } - status = *(status_t *)replyData; + + status_t status = mIEffect->command(cmdCode, cmdSize, cmdData, replySize, replyData); if (status != NO_ERROR) { return status; } - if (cmdCode == EFFECT_CMD_ENABLE) { - android_atomic_or(1, &mEnabled); - } - if (cmdCode == EFFECT_CMD_DISABLE) { - android_atomic_and(~1, &mEnabled); + if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) { + status = *(status_t *)replyData; + if (status != NO_ERROR) { + return status; + } + if (cmdCode == EFFECT_CMD_ENABLE) { + android_atomic_or(1, &mEnabled); + } else { + android_atomic_and(~1, &mEnabled); + } } + return status; } diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp index 39552b6..68f2e9b 100644 --- a/media/libmedia/Visualizer.cpp +++ b/media/libmedia/Visualizer.cpp @@ -169,11 +169,13 @@ status_t Visualizer::getWaveForm(uint8_t *waveform) status_t status = NO_ERROR; if (mEnabled) { uint32_t replySize = mCaptureSize; - status_t status = command(VISU_CMD_CAPTURE, 0, NULL, &replySize, waveform); + status = command(VISU_CMD_CAPTURE, 0, NULL, &replySize, waveform); + LOGV("getWaveForm() command returned %d", status); if (replySize == 0) { status = NOT_ENOUGH_DATA; } } else { + LOGV("getWaveForm() disabled"); memset(waveform, 0x80, mCaptureSize); } return status; @@ -191,7 +193,7 @@ status_t Visualizer::getFft(uint8_t *fft) status_t status = NO_ERROR; if (mEnabled) { uint8_t buf[mCaptureSize]; - status_t status = getWaveForm(buf); + status = getWaveForm(buf); if (status == NO_ERROR) { status = doFft(fft, buf); } diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index ffe1983..877e787 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -534,8 +534,9 @@ status_t MediaPlayerService::dump(int fd, const Vector<String16>& args) if (f) { while (!feof(f)) { fgets(buffer, SIZE, f); - if (strstr(buffer, " /sdcard/") || + if (strstr(buffer, " /mnt/sdcard/") || strstr(buffer, " /system/sounds/") || + strstr(buffer, " /data/") || strstr(buffer, " /system/media/")) { result.append(" "); result.append(buffer); @@ -569,8 +570,9 @@ status_t MediaPlayerService::dump(int fd, const Vector<String16>& args) } else { linkto[len] = 0; } - if (strstr(linkto, "/sdcard/") == linkto || + if (strstr(linkto, "/mnt/sdcard/") == linkto || strstr(linkto, "/system/sounds/") == linkto || + strstr(linkto, "/data/") == linkto || strstr(linkto, "/system/media/") == linkto) { result.append(" "); result.append(buffer); diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 3bf8ae7..472bfcf 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -18,6 +18,7 @@ LOCAL_SRC_FILES:= \ HTTPStream.cpp \ JPEGSource.cpp \ MP3Extractor.cpp \ + MPEG2TSWriter.cpp \ MPEG4Extractor.cpp \ MPEG4Writer.cpp \ MediaBuffer.cpp \ diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index c27cfc8..b314114 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -27,9 +27,13 @@ #include <media/stagefright/MediaSource.h> #include <media/stagefright/MetaData.h> +#include "include/AwesomePlayer.h" + namespace android { -AudioPlayer::AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink) +AudioPlayer::AudioPlayer( + const sp<MediaPlayerBase::AudioSink> &audioSink, + AwesomePlayer *observer) : mAudioTrack(NULL), mInputBuffer(NULL), mSampleRate(0), @@ -45,7 +49,8 @@ AudioPlayer::AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink) mIsFirstBuffer(false), mFirstBufferResult(OK), mFirstBuffer(NULL), - mAudioSink(audioSink) { + mAudioSink(audioSink), + mObserver(observer) { } AudioPlayer::~AudioPlayer() { @@ -301,6 +306,9 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) { } mSeeking = false; + if (mObserver) { + mObserver->postAudioSeekComplete(); + } } } @@ -323,6 +331,10 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) { Mutex::Autolock autoLock(mLock); if (err != OK) { + if (mObserver && !mReachedEOS) { + mObserver->postAudioEOS(); + } + mReachedEOS = true; mFinalStatus = err; break; @@ -411,6 +423,12 @@ status_t AudioPlayer::seekTo(int64_t time_us) { mReachedEOS = false; mSeekTimeUs = time_us; + if (mAudioSink != NULL) { + mAudioSink->flush(); + } else { + mAudioTrack->flush(); + } + return OK; } diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index fd5f30b..97c9003 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -642,7 +642,7 @@ status_t AwesomePlayer::play_l() { if (mAudioSource != NULL) { if (mAudioPlayer == NULL) { if (mAudioSink != NULL) { - mAudioPlayer = new AudioPlayer(mAudioSink); + mAudioPlayer = new AudioPlayer(mAudioSink, this); mAudioPlayer->setSource(mAudioSource); // We've already started the MediaSource in order to enable @@ -669,8 +669,6 @@ status_t AwesomePlayer::play_l() { } else { mAudioPlayer->resume(); } - - postCheckAudioStatusEvent_l(); } if (mTimeSource == NULL && mAudioPlayer == NULL) { @@ -1191,7 +1189,7 @@ void AwesomePlayer::postCheckAudioStatusEvent_l() { return; } mAudioStatusEventPending = true; - mQueue.postEventWithDelay(mCheckAudioStatusEvent, 100000ll); + mQueue.postEvent(mCheckAudioStatusEvent); } void AwesomePlayer::onCheckAudioStatus() { @@ -1222,8 +1220,6 @@ void AwesomePlayer::onCheckAudioStatus() { mFlags |= FIRST_FRAME; postStreamDoneEvent_l(finalStatus); } - - postCheckAudioStatusEvent_l(); } status_t AwesomePlayer::prepare() { @@ -1685,5 +1681,13 @@ uint32_t AwesomePlayer::flags() const { return mExtractorFlags; } +void AwesomePlayer::postAudioEOS() { + postCheckAudioStatusEvent_l(); +} + +void AwesomePlayer::postAudioSeekComplete() { + postCheckAudioStatusEvent_l(); +} + } // namespace android diff --git a/media/libstagefright/MPEG2TSWriter.cpp b/media/libstagefright/MPEG2TSWriter.cpp new file mode 100644 index 0000000..ee74b88 --- /dev/null +++ b/media/libstagefright/MPEG2TSWriter.cpp @@ -0,0 +1,758 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MPEG2TSWriter" +#include <media/stagefright/foundation/ADebug.h> + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MPEG2TSWriter.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +#include "include/ESDS.h" + +namespace android { + +struct MPEG2TSWriter::SourceInfo : public AHandler { + SourceInfo(const sp<MediaSource> &source); + + void start(const sp<AMessage> ¬ify); + void stop(); + + unsigned streamType() const; + unsigned incrementContinuityCounter(); + + enum { + kNotifyStartFailed, + kNotifyBuffer, + kNotifyReachedEOS, + }; + +protected: + virtual void onMessageReceived(const sp<AMessage> &msg); + + virtual ~SourceInfo(); + +private: + enum { + kWhatStart = 'strt', + kWhatRead = 'read', + }; + + sp<MediaSource> mSource; + sp<ALooper> mLooper; + sp<AMessage> mNotify; + + sp<ABuffer> mAACBuffer; + + unsigned mStreamType; + unsigned mContinuityCounter; + + void extractCodecSpecificData(); + + void appendAACFrames(MediaBuffer *buffer); + void flushAACFrames(); + + void postAVCFrame(MediaBuffer *buffer); + + DISALLOW_EVIL_CONSTRUCTORS(SourceInfo); +}; + +MPEG2TSWriter::SourceInfo::SourceInfo(const sp<MediaSource> &source) + : mSource(source), + mLooper(new ALooper), + mStreamType(0), + mContinuityCounter(0) { + mLooper->setName("MPEG2TSWriter source"); + + sp<MetaData> meta = mSource->getFormat(); + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { + mStreamType = 0x0f; + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { + mStreamType = 0x1b; + } else { + TRESPASS(); + } +} + +MPEG2TSWriter::SourceInfo::~SourceInfo() { +} + +unsigned MPEG2TSWriter::SourceInfo::streamType() const { + return mStreamType; +} + +unsigned MPEG2TSWriter::SourceInfo::incrementContinuityCounter() { + if (++mContinuityCounter == 16) { + mContinuityCounter = 0; + } + + return mContinuityCounter; +} + +void MPEG2TSWriter::SourceInfo::start(const sp<AMessage> ¬ify) { + mLooper->registerHandler(this); + mLooper->start(); + + mNotify = notify; + + (new AMessage(kWhatStart, id()))->post(); +} + +void MPEG2TSWriter::SourceInfo::stop() { + mLooper->unregisterHandler(id()); + mLooper->stop(); +} + +void MPEG2TSWriter::SourceInfo::extractCodecSpecificData() { + sp<MetaData> meta = mSource->getFormat(); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { + return; + } + + sp<ABuffer> out = new ABuffer(1024); + out->setRange(0, 0); + + uint32_t type; + const void *data; + size_t size; + CHECK(meta->findData(kKeyAVCC, &type, &data, &size)); + + const uint8_t *ptr = (const uint8_t *)data; + + size_t numSeqParameterSets = ptr[5] & 31; + + ptr += 6; + size -= 6; + + for (size_t i = 0; i < numSeqParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + CHECK_LE(out->size() + 4 + length, out->capacity()); + memcpy(out->data() + out->size(), "\x00\x00\x00\x01", 4); + memcpy(out->data() + out->size() + 4, ptr, length); + out->setRange(0, out->size() + length + 4); + + ptr += length; + size -= length; + } + + 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); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + CHECK_LE(out->size() + 4 + length, out->capacity()); + memcpy(out->data() + out->size(), "\x00\x00\x00\x01", 4); + memcpy(out->data() + out->size() + 4, ptr, length); + out->setRange(0, out->size() + length + 4); + + ptr += length; + size -= length; + } + + out->meta()->setInt64("timeUs", 0ll); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kNotifyBuffer); + notify->setObject("buffer", out); + notify->post(); +} + +void MPEG2TSWriter::SourceInfo::postAVCFrame(MediaBuffer *buffer) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kNotifyBuffer); + + sp<ABuffer> copy = + new ABuffer(buffer->range_length()); + memcpy(copy->data(), + (const uint8_t *)buffer->data() + + buffer->range_offset(), + buffer->range_length()); + + int64_t timeUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs)); + copy->meta()->setInt64("timeUs", timeUs); + + int32_t isSync; + if (buffer->meta_data()->findInt32(kKeyIsSyncFrame, &isSync) + && isSync != 0) { + copy->meta()->setInt32("isSync", true); + } + + notify->setObject("buffer", copy); + notify->post(); +} + +void MPEG2TSWriter::SourceInfo::appendAACFrames(MediaBuffer *buffer) { + if (mAACBuffer != NULL + && mAACBuffer->size() + 7 + buffer->range_length() + > mAACBuffer->capacity()) { + flushAACFrames(); + } + + if (mAACBuffer == NULL) { + size_t alloc = 4096; + if (buffer->range_length() + 7 > alloc) { + alloc = 7 + buffer->range_length(); + } + + mAACBuffer = new ABuffer(alloc); + + int64_t timeUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs)); + + mAACBuffer->meta()->setInt64("timeUs", timeUs); + mAACBuffer->meta()->setInt32("isSync", true); + + mAACBuffer->setRange(0, 0); + } + + sp<MetaData> meta = mSource->getFormat(); + uint32_t type; + const void *data; + size_t size; + CHECK(meta->findData(kKeyESDS, &type, &data, &size)); + + ESDS esds((const char *)data, size); + CHECK_EQ(esds.InitCheck(), (status_t)OK); + + const uint8_t *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + (const void **)&codec_specific_data, &codec_specific_data_size); + + CHECK_GE(codec_specific_data_size, 2u); + + unsigned profile = (codec_specific_data[0] >> 3) - 1; + + unsigned sampling_freq_index = + ((codec_specific_data[0] & 7) << 1) + | (codec_specific_data[1] >> 7); + + unsigned channel_configuration = + (codec_specific_data[1] >> 3) & 0x0f; + + uint8_t *ptr = mAACBuffer->data() + mAACBuffer->size(); + + const uint32_t aac_frame_length = buffer->range_length() + 7; + + *ptr++ = 0xff; + *ptr++ = 0xf1; // b11110001, ID=0, layer=0, protection_absent=1 + + *ptr++ = + profile << 6 + | sampling_freq_index << 2 + | ((channel_configuration >> 2) & 1); // private_bit=0 + + // original_copy=0, home=0, copyright_id_bit=0, copyright_id_start=0 + *ptr++ = + (channel_configuration & 3) << 6 + | aac_frame_length >> 11; + *ptr++ = (aac_frame_length >> 3) & 0xff; + *ptr++ = (aac_frame_length & 7) << 5; + + // adts_buffer_fullness=0, number_of_raw_data_blocks_in_frame=0 + *ptr++ = 0; + + memcpy(ptr, + (const uint8_t *)buffer->data() + buffer->range_offset(), + buffer->range_length()); + + ptr += buffer->range_length(); + + mAACBuffer->setRange(0, ptr - mAACBuffer->data()); +} + +void MPEG2TSWriter::SourceInfo::flushAACFrames() { + if (mAACBuffer == NULL) { + return; + } + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kNotifyBuffer); + notify->setObject("buffer", mAACBuffer); + notify->post(); + + mAACBuffer.clear(); +} + +void MPEG2TSWriter::SourceInfo::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatStart: + { + status_t err = mSource->start(); + if (err != OK) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kNotifyStartFailed); + notify->post(); + break; + } + + extractCodecSpecificData(); + + (new AMessage(kWhatRead, id()))->post(); + break; + } + + case kWhatRead: + { + MediaBuffer *buffer; + status_t err = mSource->read(&buffer); + + if (err != OK && err != INFO_FORMAT_CHANGED) { + if (mStreamType == 0x0f) { + flushAACFrames(); + } + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kNotifyReachedEOS); + notify->setInt32("status", err); + notify->post(); + break; + } + + if (err == OK) { + if (buffer->range_length() > 0) { + if (mStreamType == 0x0f) { + appendAACFrames(buffer); + } else { + postAVCFrame(buffer); + } + } + + buffer->release(); + buffer = NULL; + } + + msg->post(); + break; + } + + default: + TRESPASS(); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2TSWriter::MPEG2TSWriter(const char *filename) + : mFile(fopen(filename, "wb")), + mStarted(false), + mNumSourcesDone(0), + mNumTSPacketsWritten(0), + mNumTSPacketsBeforeMeta(0) { + CHECK(mFile != NULL); + + mLooper = new ALooper; + mLooper->setName("MPEG2TSWriter"); + + mReflector = new AHandlerReflector<MPEG2TSWriter>(this); + + mLooper->registerHandler(mReflector); + mLooper->start(); +} + +MPEG2TSWriter::~MPEG2TSWriter() { + mLooper->unregisterHandler(mReflector->id()); + mLooper->stop(); + + fclose(mFile); + mFile = NULL; +} + +status_t MPEG2TSWriter::addSource(const sp<MediaSource> &source) { + CHECK(!mStarted); + + sp<MetaData> meta = source->getFormat(); + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC) + && strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { + return ERROR_UNSUPPORTED; + } + + sp<SourceInfo> info = new SourceInfo(source); + + mSources.push(info); + + return OK; +} + +status_t MPEG2TSWriter::start(MetaData *param) { + CHECK(!mStarted); + + mStarted = true; + mNumSourcesDone = 0; + mNumTSPacketsWritten = 0; + mNumTSPacketsBeforeMeta = 0; + + for (size_t i = 0; i < mSources.size(); ++i) { + sp<AMessage> notify = + new AMessage(kWhatSourceNotify, mReflector->id()); + + notify->setInt32("source-index", i); + + mSources.editItemAt(i)->start(notify); + } + + return OK; +} + +status_t MPEG2TSWriter::stop() { + CHECK(mStarted); + + for (size_t i = 0; i < mSources.size(); ++i) { + mSources.editItemAt(i)->stop(); + } + mStarted = false; + + return OK; +} + +status_t MPEG2TSWriter::pause() { + CHECK(mStarted); + + return OK; +} + +bool MPEG2TSWriter::reachedEOS() { + return !mStarted || (mNumSourcesDone == mSources.size() ? true : false); +} + +status_t MPEG2TSWriter::dump(int fd, const Vector<String16> &args) { + return OK; +} + +void MPEG2TSWriter::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatSourceNotify: + { + int32_t sourceIndex; + CHECK(msg->findInt32("source-index", &sourceIndex)); + + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == SourceInfo::kNotifyReachedEOS + || what == SourceInfo::kNotifyStartFailed) { + ++mNumSourcesDone; + } else if (what == SourceInfo::kNotifyBuffer) { + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + + writeTS(); + + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + writeAccessUnit(sourceIndex, buffer); + } + break; + } + + default: + TRESPASS(); + } +} + +void MPEG2TSWriter::writeProgramAssociationTable() { + // 0x47 + // transport_error_indicator = b0 + // payload_unit_start_indicator = b1 + // transport_priority = b0 + // PID = b0000000000000 (13 bits) + // transport_scrambling_control = b00 + // adaptation_field_control = b01 (no adaptation field, payload only) + // continuity_counter = b???? + // skip = 0x00 + // --- payload follows + // table_id = 0x00 + // section_syntax_indicator = b1 + // must_be_zero = b0 + // reserved = b11 + // section_length = 0x00d + // transport_stream_id = 0x0000 + // reserved = b11 + // version_number = b00001 + // current_next_indicator = b1 + // section_number = 0x00 + // last_section_number = 0x00 + // one program follows: + // program_number = 0x0001 + // reserved = b111 + // program_map_PID = 0x01e0 (13 bits!) + // CRC = 0x???????? + + static const uint8_t kData[] = { + 0x47, + 0x40, 0x00, 0x10, 0x00, // b0100 0000 0000 0000 0001 ???? 0000 0000 + 0x00, 0xb0, 0x0d, 0x00, // b0000 0000 1011 0000 0000 1101 0000 0000 + 0x00, 0xc3, 0x00, 0x00, // b0000 0000 1100 0011 0000 0000 0000 0000 + 0x00, 0x01, 0xe1, 0xe0, // b0000 0000 0000 0001 1110 0001 1110 0000 + 0x00, 0x00, 0x00, 0x00 // b???? ???? ???? ???? ???? ???? ???? ???? + }; + + sp<ABuffer> buffer = new ABuffer(188); + memset(buffer->data(), 0, buffer->size()); + memcpy(buffer->data(), kData, sizeof(kData)); + + static const unsigned kContinuityCounter = 5; + buffer->data()[3] |= kContinuityCounter; + + CHECK_EQ(fwrite(buffer->data(), 1, buffer->size(), mFile), buffer->size()); +} + +void MPEG2TSWriter::writeProgramMap() { + // 0x47 + // transport_error_indicator = b0 + // payload_unit_start_indicator = b1 + // transport_priority = b0 + // PID = b0 0001 1110 0000 (13 bits) [0x1e0] + // transport_scrambling_control = b00 + // adaptation_field_control = b01 (no adaptation field, payload only) + // continuity_counter = b???? + // skip = 0x00 + // -- payload follows + // table_id = 0x02 + // section_syntax_indicator = b1 + // must_be_zero = b0 + // reserved = b11 + // section_length = 0x??? + // program_number = 0x0001 + // reserved = b11 + // version_number = b00001 + // current_next_indicator = b1 + // section_number = 0x00 + // last_section_number = 0x00 + // reserved = b111 + // PCR_PID = b? ???? ???? ???? (13 bits) + // reserved = b1111 + // program_info_length = 0x000 + // one or more elementary stream descriptions follow: + // stream_type = 0x?? + // reserved = b111 + // elementary_PID = b? ???? ???? ???? (13 bits) + // reserved = b1111 + // ES_info_length = 0x000 + // CRC = 0x???????? + + static const uint8_t kData[] = { + 0x47, + 0x41, 0xe0, 0x10, 0x00, // b0100 0001 1110 0000 0001 ???? 0000 0000 + 0x02, 0xb0, 0x00, 0x00, // b0000 0010 1011 ???? ???? ???? 0000 0000 + 0x01, 0xc3, 0x00, 0x00, // b0000 0001 1100 0011 0000 0000 0000 0000 + 0xe0, 0x00, 0xf0, 0x00 // b111? ???? ???? ???? 1111 0000 0000 0000 + }; + + sp<ABuffer> buffer = new ABuffer(188); + memset(buffer->data(), 0, buffer->size()); + memcpy(buffer->data(), kData, sizeof(kData)); + + static const unsigned kContinuityCounter = 5; + buffer->data()[3] |= kContinuityCounter; + + size_t section_length = 5 * mSources.size() + 4 + 9; + buffer->data()[6] |= section_length >> 8; + buffer->data()[7] = section_length & 0xff; + + static const unsigned kPCR_PID = 0x1e1; + buffer->data()[13] |= (kPCR_PID >> 8) & 0x1f; + buffer->data()[14] = kPCR_PID & 0xff; + + uint8_t *ptr = &buffer->data()[sizeof(kData)]; + for (size_t i = 0; i < mSources.size(); ++i) { + *ptr++ = mSources.editItemAt(i)->streamType(); + + const unsigned ES_PID = 0x1e0 + i + 1; + *ptr++ = 0xe0 | (ES_PID >> 8); + *ptr++ = ES_PID & 0xff; + *ptr++ = 0xf0; + *ptr++ = 0x00; + } + + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x00; + + CHECK_EQ(fwrite(buffer->data(), 1, buffer->size(), mFile), buffer->size()); +} + +void MPEG2TSWriter::writeAccessUnit( + int32_t sourceIndex, const sp<ABuffer> &accessUnit) { + // 0x47 + // transport_error_indicator = b0 + // payload_unit_start_indicator = b1 + // transport_priority = b0 + // PID = b0 0001 1110 ???? (13 bits) [0x1e0 + 1 + sourceIndex] + // transport_scrambling_control = b00 + // adaptation_field_control = b01 (no adaptation field, payload only) + // continuity_counter = b???? + // -- payload follows + // packet_startcode_prefix = 0x000001 + // stream_id = 0x?? (0xe0 for avc video, 0xc0 for aac audio) + // PES_packet_length = 0x???? + // reserved = b10 + // PES_scrambling_control = b00 + // PES_priority = b0 + // data_alignment_indicator = b1 + // copyright = b0 + // original_or_copy = b0 + // PTS_DTS_flags = b10 (PTS only) + // ESCR_flag = b0 + // ES_rate_flag = b0 + // DSM_trick_mode_flag = b0 + // additional_copy_info_flag = b0 + // PES_CRC_flag = b0 + // PES_extension_flag = b0 + // PES_header_data_length = 0x05 + // reserved = b0010 (PTS) + // PTS[32..30] = b??? + // reserved = b1 + // PTS[29..15] = b??? ???? ???? ???? (15 bits) + // reserved = b1 + // PTS[14..0] = b??? ???? ???? ???? (15 bits) + // reserved = b1 + // the first fragment of "buffer" follows + + sp<ABuffer> buffer = new ABuffer(188); + memset(buffer->data(), 0, buffer->size()); + + const unsigned PID = 0x1e0 + sourceIndex + 1; + + const unsigned continuity_counter = + mSources.editItemAt(sourceIndex)->incrementContinuityCounter(); + + // XXX if there are multiple streams of a kind (more than 1 audio or + // more than 1 video) they need distinct stream_ids. + const unsigned stream_id = + mSources.editItemAt(sourceIndex)->streamType() == 0x0f ? 0xc0 : 0xe0; + + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + uint32_t PTS = (timeUs * 9ll) / 100ll; + + size_t PES_packet_length = accessUnit->size() + 8; + + uint8_t *ptr = buffer->data(); + *ptr++ = 0x47; + *ptr++ = 0x40 | (PID >> 8); + *ptr++ = PID & 0xff; + *ptr++ = 0x10 | continuity_counter; + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x01; + *ptr++ = stream_id; + *ptr++ = PES_packet_length >> 8; + *ptr++ = PES_packet_length & 0xff; + *ptr++ = 0x84; + *ptr++ = 0x80; + *ptr++ = 0x05; + *ptr++ = 0x20 | (((PTS >> 30) & 7) << 1) | 1; + *ptr++ = (PTS >> 22) & 0xff; + *ptr++ = (((PTS >> 15) & 0x7f) << 1) | 1; + *ptr++ = (PTS >> 7) & 0xff; + *ptr++ = ((PTS & 0x7f) << 1) | 1; + + size_t sizeLeft = buffer->data() + buffer->size() - ptr; + size_t copy = accessUnit->size(); + if (copy > sizeLeft) { + copy = sizeLeft; + } + + memcpy(ptr, accessUnit->data(), copy); + + CHECK_EQ(fwrite(buffer->data(), 1, buffer->size(), mFile), buffer->size()); + + size_t offset = copy; + while (offset < accessUnit->size()) { + // for subsequent fragments of "buffer": + // 0x47 + // transport_error_indicator = b0 + // payload_unit_start_indicator = b0 + // transport_priority = b0 + // PID = b0 0001 1110 ???? (13 bits) [0x1e0 + 1 + sourceIndex] + // transport_scrambling_control = b00 + // adaptation_field_control = b01 (no adaptation field, payload only) + // continuity_counter = b???? + // the fragment of "buffer" follows. + + memset(buffer->data(), 0, buffer->size()); + + const unsigned continuity_counter = + mSources.editItemAt(sourceIndex)->incrementContinuityCounter(); + + ptr = buffer->data(); + *ptr++ = 0x47; + *ptr++ = 0x00 | (PID >> 8); + *ptr++ = PID & 0xff; + *ptr++ = 0x10 | continuity_counter; + + size_t sizeLeft = buffer->data() + buffer->size() - ptr; + size_t copy = accessUnit->size() - offset; + if (copy > sizeLeft) { + copy = sizeLeft; + } + + memcpy(ptr, accessUnit->data() + offset, copy); + CHECK_EQ(fwrite(buffer->data(), 1, buffer->size(), mFile), + buffer->size()); + + offset += copy; + } +} + +void MPEG2TSWriter::writeTS() { + if (mNumTSPacketsWritten >= mNumTSPacketsBeforeMeta) { + writeProgramAssociationTable(); + writeProgramMap(); + + mNumTSPacketsBeforeMeta = mNumTSPacketsWritten + 2500; + } +} + +} // namespace android + diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index ba1e218..e6c2f7e 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -1541,20 +1541,52 @@ status_t MPEG4Writer::Track::threadEntry() { int32_t isSync = false; meta_data->findInt32(kKeyIsSyncFrame, &isSync); + /* + * The original timestamp found in the data buffer will be modified as below: + * + * There is a playback offset into this track if the track's start time + * is not the same as the movie start time, which will be recorded in edst + * box of the output file. The playback offset is to make sure that the + * starting time of the audio/video tracks are synchronized. Although the + * track's media timestamp may be subject to various modifications + * as outlined below, the track's playback offset time remains unchanged + * once the first data buffer of the track is received. + * + * The media time stamp will be calculated by subtracting the playback offset + * (and potential pause durations) from the original timestamp in the buffer. + * + * If this track is a video track for a real-time recording application with + * both audio and video tracks, its media timestamp will subject to further + * modification based on the media clock of the audio track. This modification + * is needed for the purpose of maintaining good audio/video synchronization. + * + * If the recording session is paused and resumed multiple times, the track + * media timestamp will be modified as if the recording session had never been + * paused at all during playback of the recorded output file. In other words, + * the output file will have no memory of pause/resume durations. + * + */ CHECK(meta_data->findInt64(kKeyTime, ×tampUs)); + LOGV("%s timestampUs: %lld", mIsAudio? "Audio": "Video", timestampUs); //////////////////////////////////////////////////////////////////////////////// if (mSampleSizes.empty()) { mStartTimestampUs = timestampUs; mOwner->setStartTimestampUs(mStartTimestampUs); + previousPausedDurationUs = mStartTimestampUs; } if (mResumed) { - previousPausedDurationUs += (timestampUs - mTrackDurationUs - lastDurationUs); + int64_t durExcludingEarlierPausesUs = timestampUs - previousPausedDurationUs; + CHECK(durExcludingEarlierPausesUs >= 0); + int64_t pausedDurationUs = durExcludingEarlierPausesUs - mTrackDurationUs; + CHECK(pausedDurationUs >= lastDurationUs); + previousPausedDurationUs += pausedDurationUs - lastDurationUs; mResumed = false; } timestampUs -= previousPausedDurationUs; + CHECK(timestampUs >= 0); if (mIsRealTimeRecording && !mIsAudio) { // The minor adjustment on the timestamp is heuristic/experimental // We are adjusting the timestamp to reduce the fluctuation of the duration @@ -1590,8 +1622,8 @@ status_t MPEG4Writer::Track::threadEntry() { } } - LOGV("time stamp: %lld and previous paused duration %lld", - timestampUs, previousPausedDurationUs); + LOGV("%s media time stamp: %lld and previous paused duration %lld", + mIsAudio? "Audio": "Video", timestampUs, previousPausedDurationUs); if (timestampUs > mTrackDurationUs) { mTrackDurationUs = timestampUs; } @@ -1873,6 +1905,7 @@ void MPEG4Writer::Track::writeTrackHeader( // First elst entry: specify the starting time offset int64_t offsetUs = mStartTimestampUs - moovStartTimeUs; + LOGV("OffsetUs: %lld", offsetUs); int32_t seg = (offsetUs * mvhdTimeScale + 5E5) / 1E6; mOwner->writeInt32(seg); // in mvhd timecale mOwner->writeInt32(-1); // starting time offset diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index 332bab3..2743b2f 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -96,6 +96,11 @@ status_t NuHTTPDataSource::connect( return connect(host, port, path, headers, offset); } +static bool IsRedirectStatusCode(int httpStatus) { + return httpStatus == 301 || httpStatus == 302 + || httpStatus == 303 || httpStatus == 307; +} + status_t NuHTTPDataSource::connect( const char *host, unsigned port, const char *path, const String8 &headers, @@ -161,7 +166,7 @@ status_t NuHTTPDataSource::connect( return err; } - if (httpStatus == 302) { + if (IsRedirectStatusCode(httpStatus)) { string value; CHECK(mHTTP.find_header_value("Location", &value)); diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp index 7a8cf32..43938b2 100644 --- a/media/libstagefright/OggExtractor.cpp +++ b/media/libstagefright/OggExtractor.cpp @@ -93,7 +93,10 @@ private: sp<DataSource> mSource; off_t mOffset; Page mCurrentPage; + uint64_t mPrevGranulePosition; size_t mCurrentPageSize; + bool mFirstPacketInPage; + uint64_t mCurrentPageSamples; size_t mNextLaceIndex; off_t mFirstDataOffset; @@ -113,6 +116,8 @@ private: void parseFileMetaData(); void extractAlbumArt(const void *data, size_t size); + uint64_t findPrevGranulePosition(off_t pageOffset); + MyVorbisExtractor(const MyVorbisExtractor &); MyVorbisExtractor &operator=(const MyVorbisExtractor &); }; @@ -193,7 +198,10 @@ status_t OggSource::read( MyVorbisExtractor::MyVorbisExtractor(const sp<DataSource> &source) : mSource(source), mOffset(0), + mPrevGranulePosition(0), mCurrentPageSize(0), + mFirstPacketInPage(true), + mCurrentPageSamples(0), mNextLaceIndex(0), mFirstDataOffset(-1) { mCurrentPage.mNumSegments = 0; @@ -238,6 +246,52 @@ status_t MyVorbisExtractor::findNextPage( } } +// Given the offset of the "current" page, find the page immediately preceding +// it (if any) and return its granule position. +// To do this we back up from the "current" page's offset until we find any +// page preceding it and then scan forward to just before the current page. +uint64_t MyVorbisExtractor::findPrevGranulePosition(off_t pageOffset) { + off_t prevPageOffset = 0; + off_t prevGuess = pageOffset; + for (;;) { + if (prevGuess >= 5000) { + prevGuess -= 5000; + } else { + prevGuess = 0; + } + + LOGV("backing up %ld bytes", pageOffset - prevGuess); + + CHECK_EQ(findNextPage(prevGuess, &prevPageOffset), (status_t)OK); + + if (prevPageOffset < pageOffset || prevGuess == 0) { + break; + } + } + + if (prevPageOffset == pageOffset) { + // We did not find a page preceding this one. + return 0; + } + + LOGV("prevPageOffset at %ld, pageOffset at %ld", prevPageOffset, pageOffset); + + for (;;) { + Page prevPage; + ssize_t n = readPage(prevPageOffset, &prevPage); + + if (n <= 0) { + return 0; + } + + prevPageOffset += n; + + if (prevPageOffset == pageOffset) { + return prevPage.mGranulePosition; + } + } +} + status_t MyVorbisExtractor::seekToOffset(off_t offset) { if (mFirstDataOffset >= 0 && offset < mFirstDataOffset) { // Once we know where the actual audio data starts (past the headers) @@ -252,9 +306,16 @@ status_t MyVorbisExtractor::seekToOffset(off_t offset) { return err; } + // We found the page we wanted to seek to, but we'll also need + // the page preceding it to determine how many valid samples are on + // this page. + mPrevGranulePosition = findPrevGranulePosition(pageOffset); + mOffset = pageOffset; mCurrentPageSize = 0; + mFirstPacketInPage = true; + mCurrentPageSamples = 0; mCurrentPage.mNumSegments = 0; mNextLaceIndex = 0; @@ -399,6 +460,12 @@ status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) { buffer->meta_data()->setInt64(kKeyTime, timeUs); } + if (mFirstPacketInPage) { + buffer->meta_data()->setInt32( + kKeyValidSamples, mCurrentPageSamples); + mFirstPacketInPage = false; + } + *out = buffer; return OK; @@ -423,6 +490,12 @@ status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) { return n < 0 ? n : (status_t)ERROR_END_OF_STREAM; } + mCurrentPageSamples = + mCurrentPage.mGranulePosition - mPrevGranulePosition; + mFirstPacketInPage = true; + + mPrevGranulePosition = mCurrentPage.mGranulePosition; + mCurrentPageSize = n; mNextLaceIndex = 0; @@ -435,6 +508,10 @@ status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) { buffer->meta_data()->setInt64(kKeyTime, timeUs); } + buffer->meta_data()->setInt32( + kKeyValidSamples, mCurrentPageSamples); + mFirstPacketInPage = false; + *out = buffer; return OK; diff --git a/media/libstagefright/codecs/aacdec/AACDecoder.cpp b/media/libstagefright/codecs/aacdec/AACDecoder.cpp index e4ed5e6..f58c16d 100644 --- a/media/libstagefright/codecs/aacdec/AACDecoder.cpp +++ b/media/libstagefright/codecs/aacdec/AACDecoder.cpp @@ -171,6 +171,10 @@ status_t AACDecoder::read( mInputBuffer->release(); mInputBuffer = NULL; } + + // Make sure that the next buffer output does not still + // depend on fragments from the last one decoded. + PVMP4AudioDecoderResetBuffer(mDecoderBuf); } else { seekTimeUs = -1; } diff --git a/media/libstagefright/codecs/amrnb/common/include/frame_type_3gpp.h b/media/libstagefright/codecs/amrnb/common/include/frame_type_3gpp.h index 0620367..3ebd929 100644 --- a/media/libstagefright/codecs/amrnb/common/include/frame_type_3gpp.h +++ b/media/libstagefright/codecs/amrnb/common/include/frame_type_3gpp.h @@ -87,22 +87,22 @@ extern "C" enum Frame_Type_3GPP { - AMR_475 = 0, - AMR_515, - AMR_59, - AMR_67, - AMR_74, - AMR_795, - AMR_102, - AMR_122, - AMR_SID, - GSM_EFR_SID, - TDMA_EFR_SID, - PDC_EFR_SID, - FOR_FUTURE_USE1, - FOR_FUTURE_USE2, - FOR_FUTURE_USE3, - AMR_NO_DATA + AMR_475 = 0, /* 4.75 kbps */ + AMR_515, /* 5.15 kbps */ + AMR_59, /* 5.9 kbps */ + AMR_67, /* 6.7 kbps */ + AMR_74, /* 7.4 kbps */ + AMR_795, /* 7.95 kbps */ + AMR_102, /* 10.2 kbps */ + AMR_122, /* 12.2 kbps */ + AMR_SID, /* GSM AMR DTX */ + GSM_EFR_SID, /* GSM EFR DTX */ + TDMA_EFR_SID, /* TDMA EFR DTX */ + PDC_EFR_SID, /* PDC EFR DTX */ + FOR_FUTURE_USE1, /* Unused 1 */ + FOR_FUTURE_USE2, /* Unused 2 */ + FOR_FUTURE_USE3, /* Unused 3 */ + AMR_NO_DATA /* No data */ }; /*---------------------------------------------------------------------------- diff --git a/media/libstagefright/codecs/amrnb/dec/src/gsmamr_dec.h b/media/libstagefright/codecs/amrnb/dec/src/gsmamr_dec.h index 673a94a..a9fdb1c 100644 --- a/media/libstagefright/codecs/amrnb/dec/src/gsmamr_dec.h +++ b/media/libstagefright/codecs/amrnb/dec/src/gsmamr_dec.h @@ -87,6 +87,8 @@ terms listed above has been obtained from the copyright holder. #include "gsm_amr_typedefs.h" #include "pvamrnbdecoder_api.h" +#include "frame_type_3gpp.h" + /*--------------------------------------------------------------------------*/ #ifdef __cplusplus extern "C" @@ -117,25 +119,6 @@ extern "C" /*---------------------------------------------------------------------------- ; ENUMERATED TYPEDEF'S ----------------------------------------------------------------------------*/ - enum Frame_Type_3GPP - { - AMR_475 = 0, /* 4.75 kbps */ - AMR_515, /* 5.15 kbps */ - AMR_59, /* 5.9 kbps */ - AMR_67, /* 6.7 kbps */ - AMR_74, /* 7.4 kbps */ - AMR_795, /* 7.95 kbps */ - AMR_102, /* 10.2 kbps */ - AMR_122, /* 12.2 kbps */ - AMR_SID, /* GSM AMR DTX */ - GSM_EFR_SID, /* GSM EFR DTX */ - TDMA_EFR_SID, /* TDMA EFR DTX */ - PDC_EFR_SID, /* PDC EFR DTX */ - FOR_FUTURE_USE1, /* Unused 1 */ - FOR_FUTURE_USE2, /* Unused 2 */ - FOR_FUTURE_USE3, /* Unused 3 */ - AMR_NO_DATA - }; /* No data */ /*---------------------------------------------------------------------------- ; STRUCTURES TYPEDEF'S diff --git a/media/libstagefright/codecs/amrnb/enc/src/gsmamr_enc.h b/media/libstagefright/codecs/amrnb/enc/src/gsmamr_enc.h index 390a44d..67c7aa3 100644 --- a/media/libstagefright/codecs/amrnb/enc/src/gsmamr_enc.h +++ b/media/libstagefright/codecs/amrnb/enc/src/gsmamr_enc.h @@ -86,6 +86,7 @@ terms listed above has been obtained from the copyright holder. ----------------------------------------------------------------------------*/ #include "gsm_amr_typedefs.h" +#include "frame_type_3gpp.h" /*--------------------------------------------------------------------------*/ #ifdef __cplusplus @@ -137,26 +138,6 @@ extern "C" N_MODES /* Not Used */ }; - enum Frame_Type_3GPP - { - AMR_475 = 0, /* 4.75 kbps */ - AMR_515, /* 5.15 kbps */ - AMR_59, /* 5.9 kbps */ - AMR_67, /* 6.7 kbps */ - AMR_74, /* 7.4 kbps */ - AMR_795, /* 7.95 kbps */ - AMR_102, /* 10.2 kbps */ - AMR_122, /* 12.2 kbps */ - AMR_SID, /* GSM AMR DTX */ - GSM_EFR_SID, /* GSM EFR DTX */ - TDMA_EFR_SID, /* TDMA EFR DTX */ - PDC_EFR_SID, /* PDC EFR DTX */ - FOR_FUTURE_USE1, /* Unused 1 */ - FOR_FUTURE_USE2, /* Unused 2 */ - FOR_FUTURE_USE3, /* Unused 3 */ - AMR_NO_DATA /* No data */ - }; - /*---------------------------------------------------------------------------- ; STRUCTURES TYPEDEF'S ----------------------------------------------------------------------------*/ diff --git a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp index c4a8280..59dd740 100644 --- a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp +++ b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp @@ -132,6 +132,10 @@ status_t MP3Decoder::read( mInputBuffer->release(); mInputBuffer = NULL; } + + // Make sure that the next buffer output does not still + // depend on fragments from the last one decoded. + pvmp3_InitDecoder(mConfig, mDecoderBuf); } else { seekTimeUs = -1; } diff --git a/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp b/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp index 53f0638..703b41e 100644 --- a/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp +++ b/media/libstagefright/codecs/vorbis/dec/VorbisDecoder.cpp @@ -14,6 +14,10 @@ * limitations under the License. */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "VorbisDecoder" +#include <utils/Log.h> + #include "VorbisDecoder.h" #include <media/stagefright/MediaBufferGroup.h> @@ -108,6 +112,7 @@ status_t VorbisDecoder::start(MetaData *params) { mAnchorTimeUs = 0; mNumFramesOutput = 0; + mNumFramesLeftOnPage = 0; mStarted = true; return OK; @@ -188,6 +193,13 @@ int VorbisDecoder::decodePacket(MediaBuffer *packet, MediaBuffer *out) { } } + if (numFrames > mNumFramesLeftOnPage) { + LOGV("discarding %d frames at end of page", + numFrames - mNumFramesLeftOnPage); + numFrames = mNumFramesLeftOnPage; + } + mNumFramesLeftOnPage -= numFrames; + out->set_range(0, numFrames * sizeof(int16_t) * mNumChannels); return numFrames; @@ -226,6 +238,12 @@ status_t VorbisDecoder::read( CHECK(seekTimeUs < 0); } + int32_t numPageSamples; + if (inputBuffer->meta_data()->findInt32( + kKeyValidSamples, &numPageSamples)) { + mNumFramesLeftOnPage = numPageSamples; + } + MediaBuffer *outputBuffer; CHECK_EQ(mBufferGroup->acquire_buffer(&outputBuffer), OK); diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index ea2f7d5..db98253 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -93,6 +93,9 @@ struct AwesomePlayer { // This is a mask of MediaExtractor::Flags. uint32_t flags() const; + void postAudioEOS(); + void postAudioSeekComplete(); + private: friend struct AwesomeEvent; diff --git a/media/libstagefright/include/VorbisDecoder.h b/media/libstagefright/include/VorbisDecoder.h index e9a488a..13e8b77 100644 --- a/media/libstagefright/include/VorbisDecoder.h +++ b/media/libstagefright/include/VorbisDecoder.h @@ -55,6 +55,7 @@ private: int32_t mSampleRate; int64_t mAnchorTimeUs; int64_t mNumFramesOutput; + int32_t mNumFramesLeftOnPage; vorbis_dsp_state *mState; vorbis_info *mVi; diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp index 9952783..47cca80 100644 --- a/media/libstagefright/mpeg2ts/ATSParser.cpp +++ b/media/libstagefright/mpeg2ts/ATSParser.cpp @@ -389,20 +389,23 @@ void ATSParser::Stream::parsePES(ABitReader *br) { // ES data follows. - onPayloadData( - PTS_DTS_flags, PTS, DTS, - br->data(), br->numBitsLeft() / 8); - if (PES_packet_length != 0) { CHECK_GE(PES_packet_length, PES_header_data_length + 3); unsigned dataLength = PES_packet_length - 3 - PES_header_data_length; - CHECK_EQ(br->numBitsLeft(), dataLength * 8); + CHECK_GE(br->numBitsLeft(), dataLength * 8); + + onPayloadData( + PTS_DTS_flags, PTS, DTS, br->data(), dataLength); br->skipBits(dataLength * 8); } else { + onPayloadData( + PTS_DTS_flags, PTS, DTS, + br->data(), br->numBitsLeft() / 8); + size_t payloadSizeBits = br->numBitsLeft(); CHECK((payloadSizeBits % 8) == 0); @@ -491,7 +494,7 @@ static sp<ABuffer> MakeAVCCodecSpecificData( CHECK(picParamSet != NULL); buffer->setRange(stopOffset, size - stopOffset); - LOGI("buffer has %d bytes left.", buffer->size()); + LOGV("buffer has %d bytes left.", buffer->size()); size_t csdSize = 1 + 3 + 1 + 1 @@ -527,6 +530,8 @@ static bool getNextNALUnit( const uint8_t *data = *_data; size_t size = *_size; + // hexdump(data, size); + *nalStart = NULL; *nalSize = 0; @@ -572,18 +577,23 @@ static bool getNextNALUnit( ++offset; } - CHECK_LT(offset + 2, size); - *nalStart = &data[startOffset]; *nalSize = endOffset - startOffset; - *_data = &data[offset]; - *_size = size - offset; + if (offset + 2 < size) { + *_data = &data[offset]; + *_size = size - offset; + } else { + *_data = NULL; + *_size = 0; + } return true; } sp<ABuffer> MakeCleanAVCData(const uint8_t *data, size_t size) { + // hexdump(data, size); + const uint8_t *tmpData = data; size_t tmpSize = size; @@ -591,6 +601,7 @@ sp<ABuffer> MakeCleanAVCData(const uint8_t *data, size_t size) { const uint8_t *nalStart; size_t nalSize; while (getNextNALUnit(&tmpData, &tmpSize, &nalStart, &nalSize)) { + // hexdump(nalStart, nalSize); totalSize += 4 + nalSize; } @@ -615,15 +626,15 @@ static sp<ABuffer> FindMPEG2ADTSConfig( CHECK_EQ(br.getBits(2), 0u); br.getBits(1); // protection_absent unsigned profile = br.getBits(2); - LOGI("profile = %u", profile); + LOGV("profile = %u", profile); CHECK_NE(profile, 3u); unsigned sampling_freq_index = br.getBits(4); br.getBits(1); // private_bit unsigned channel_configuration = br.getBits(3); CHECK_NE(channel_configuration, 0u); - LOGI("sampling_freq_index = %u", sampling_freq_index); - LOGI("channel_configuration = %u", channel_configuration); + LOGV("sampling_freq_index = %u", sampling_freq_index); + LOGV("channel_configuration = %u", channel_configuration); CHECK_LE(sampling_freq_index, 11u); static const int32_t kSamplingFreq[] = { @@ -707,8 +718,8 @@ void ATSParser::Stream::onPayloadData( sp<ABuffer> csd = FindMPEG2ADTSConfig(buffer, &sampleRate, &channelCount); - LOGI("sampleRate = %d", sampleRate); - LOGI("channelCount = %d", channelCount); + LOGV("sampleRate = %d", sampleRate); + LOGV("channelCount = %d", channelCount); meta->setInt32(kKeySampleRate, sampleRate); meta->setInt32(kKeyChannelCount, channelCount); @@ -716,7 +727,7 @@ void ATSParser::Stream::onPayloadData( meta->setData(kKeyESDS, 0, csd->data(), csd->size()); } - LOGI("created source!"); + LOGV("created source!"); mSource = new AnotherPacketSource(meta); // fall through @@ -915,7 +926,10 @@ void ATSParser::parseTS(ABitReader *br) { unsigned adaptation_field_control = br->getBits(2); LOGV("adaptation_field_control = %u", adaptation_field_control); - MY_LOGV("continuity_counter = %u", br->getBits(4)); + unsigned continuity_counter = br->getBits(4); + LOGV("continuity_counter = %u", continuity_counter); + + // LOGI("PID = 0x%04x, continuity_counter = %u", PID, continuity_counter); if (adaptation_field_control == 2 || adaptation_field_control == 3) { parseAdaptationField(br); diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp index 56ca375..2417305 100644 --- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp +++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp @@ -32,6 +32,8 @@ namespace android { +static const size_t kTSPacketSize = 188; + struct MPEG2TSSource : public MediaSource { MPEG2TSSource( const sp<MPEG2TSExtractor> &extractor, @@ -126,27 +128,37 @@ sp<MetaData> MPEG2TSExtractor::getMetaData() { void MPEG2TSExtractor::init() { bool haveAudio = false; bool haveVideo = false; + int numPacketsParsed = 0; while (feedMore() == OK) { ATSParser::SourceType type; if (haveAudio && haveVideo) { break; } - if (haveVideo) { - type = ATSParser::MPEG2ADTS_AUDIO; - } else { - type = ATSParser::AVC_VIDEO; + if (!haveVideo) { + sp<AnotherPacketSource> impl = + (AnotherPacketSource *)mParser->getSource( + ATSParser::AVC_VIDEO).get(); + + if (impl != NULL) { + haveVideo = true; + mSourceImpls.push(impl); + } } - sp<AnotherPacketSource> impl = - (AnotherPacketSource *)mParser->getSource(type).get(); - if (impl != NULL) { - if (type == ATSParser::MPEG2ADTS_AUDIO) { + if (!haveAudio) { + sp<AnotherPacketSource> impl = + (AnotherPacketSource *)mParser->getSource( + ATSParser::MPEG2ADTS_AUDIO).get(); + + if (impl != NULL) { haveAudio = true; - } else { - haveVideo = true; + mSourceImpls.push(impl); } - mSourceImpls.push(impl); + } + + if (++numPacketsParsed > 1500) { + break; } } @@ -156,8 +168,6 @@ void MPEG2TSExtractor::init() { status_t MPEG2TSExtractor::feedMore() { Mutex::Autolock autoLock(mLock); - static const size_t kTSPacketSize = 188; - uint8_t packet[kTSPacketSize]; ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize); @@ -176,23 +186,18 @@ status_t MPEG2TSExtractor::feedMore() { bool SniffMPEG2TS( const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *) { -#if 0 - char header; - if (source->readAt(0, &header, 1) != 1 || header != 0x47) { - return false; + for (int i = 0; i < 5; ++i) { + char header; + if (source->readAt(kTSPacketSize * i, &header, 1) != 1 + || header != 0x47) { + return false; + } } - *confidence = 0.05f; + *confidence = 0.1f; mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2TS); return true; -#else - // For now we're going to never identify this type of stream, since we'd - // just base our decision on a single byte... - // Instead you can instantiate an MPEG2TSExtractor by explicitly stating - // its proper mime type in the call to MediaExtractor::Create(...). - return false; -#endif } } // namespace android diff --git a/media/mtp/MtpCursor.cpp b/media/mtp/MtpCursor.cpp index 865a294..35d90dc 100644 --- a/media/mtp/MtpCursor.cpp +++ b/media/mtp/MtpCursor.cpp @@ -399,8 +399,7 @@ bool MtpCursor::prepareRow(CursorWindow* window) { } -bool MtpCursor::putLong(CursorWindow* window, int value, int row, int column) { - +bool MtpCursor::putLong(CursorWindow* window, int64_t value, int row, int column) { if (!window->putLong(row, column, value)) { window->freeLastRow(); LOGE("Failed allocating space for a long in column %d", column); diff --git a/media/mtp/MtpCursor.h b/media/mtp/MtpCursor.h index 9e9833f..2e03c29 100644 --- a/media/mtp/MtpCursor.h +++ b/media/mtp/MtpCursor.h @@ -67,7 +67,7 @@ private: MtpObjectHandle objectID, int row); bool prepareRow(CursorWindow* window); - bool putLong(CursorWindow* window, int value, int row, int column); + bool putLong(CursorWindow* window, int64_t value, int row, int column); bool putString(CursorWindow* window, const char* text, int row, int column); bool putThumbnail(CursorWindow* window, MtpObjectHandle objectID, MtpObjectFormat format, int row, int column); diff --git a/native/android/Android.mk b/native/android/Android.mk index cc35a3a..44ec83f 100644 --- a/native/android/Android.mk +++ b/native/android/Android.mk @@ -12,6 +12,7 @@ LOCAL_SRC_FILES:= \ looper.cpp \ native_activity.cpp \ native_window.cpp \ + obb.cpp \ sensor.cpp \ storage_manager.cpp diff --git a/native/android/obb.cpp b/native/android/obb.cpp new file mode 100644 index 0000000..e0cb1a6 --- /dev/null +++ b/native/android/obb.cpp @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#define LOG_TAG "NObb" + +#include <android/obb.h> + +#include <utils/Log.h> +#include <utils/ObbFile.h> + +using namespace android; + +struct AObbInfo : public ObbFile {}; + +AObbInfo* AObbScanner_getObbInfo(const char* filename) { + AObbInfo* obbFile = new AObbInfo(); + if (obbFile == NULL || !obbFile->readFrom(filename)) { + delete obbFile; + return NULL; + } + obbFile->incStrong((void*)AObbScanner_getObbInfo); + return static_cast<AObbInfo*>(obbFile); +} + +void AObbInfo_delete(AObbInfo* obbInfo) { + if (obbInfo != NULL) { + obbInfo->decStrong((void*)AObbScanner_getObbInfo); + } +} + +const char* AObbInfo_getPackageName(AObbInfo* obbInfo) { + return obbInfo->getPackageName(); +} + +int32_t AObbInfo_getVersion(AObbInfo* obbInfo) { + return obbInfo->getVersion(); +} + +int32_t AObbInfo_getFlags(AObbInfo* obbInfo) { + return obbInfo->getFlags(); +} diff --git a/native/android/storage_manager.cpp b/native/android/storage_manager.cpp index 6dbe746..2f20641 100644 --- a/native/android/storage_manager.cpp +++ b/native/android/storage_manager.cpp @@ -38,20 +38,20 @@ public: mStorageManager(mgr) {} - virtual void onObbResult(const android::String16& filename, const android::String16& state) { - LOGD("Got obb result (%s, %s)\n", String8(filename).string(), String8(state).string()); - } + virtual void onObbResult(const android::String16& filename, const android::String16& state); }; struct AStorageManager : public RefBase { protected: - void* mObbCallback; + AStorageManager_obbCallbackFunc mObbCallback; + void* mObbCallbackData; sp<ObbActionListener> mObbActionListener; sp<IMountService> mMountService; public: - AStorageManager() : - mObbCallback(NULL) + AStorageManager() + : mObbCallback(NULL) + , mObbCallbackData(NULL) { } @@ -73,8 +73,15 @@ public: return true; } - void setObbCallback(void* cb) { + void setObbCallback(AStorageManager_obbCallbackFunc cb, void* data) { mObbCallback = cb; + mObbCallbackData = data; + } + + void fireCallback(const char* filename, const char* state) { + if (mObbCallback != NULL) { + mObbCallback(filename, state, mObbCallbackData); + } } void mountObb(const char* filename, const char* key) { @@ -85,7 +92,7 @@ public: void unmountObb(const char* filename, const bool force) { String16 filename16(filename); - mMountService->unmountObb(filename16, force); + mMountService->unmountObb(filename16, force, mObbActionListener); } int isObbMounted(const char* filename) { @@ -104,6 +111,10 @@ public: } }; +void ObbActionListener::onObbResult(const android::String16& filename, const android::String16& state) { + mStorageManager->fireCallback(String8(filename).string(), String8(state).string()); +} + AStorageManager* AStorageManager_new() { sp<AStorageManager> mgr = new AStorageManager(); @@ -120,8 +131,8 @@ void AStorageManager_delete(AStorageManager* mgr) { } } -void AStorageManager_setObbCallback(AStorageManager* mgr, void* cb) { - mgr->setObbCallback(cb); +void AStorageManager_setObbCallback(AStorageManager* mgr, AStorageManager_obbCallbackFunc cb, void* data) { + mgr->setObbCallback(cb, data); } void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key) { diff --git a/native/include/android/obb.h b/native/include/android/obb.h new file mode 100644 index 0000000..65e9b2a --- /dev/null +++ b/native/include/android/obb.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + + +#ifndef ANDROID_OBB_H +#define ANDROID_OBB_H + +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct AObbInfo; +typedef struct AObbInfo AObbInfo; + +enum { + AOBBINFO_OVERLAY = 0x0001, +}; + +/** + * Scan an OBB and get information about it. + */ +AObbInfo* AObbScanner_getObbInfo(const char* filename); + +/** + * Destroy the AObbInfo object. You must call this when finished with the object. + */ +void AObbInfo_delete(AObbInfo* obbInfo); + +/** + * Get the package name for the OBB. + */ +const char* AObbInfo_getPackageName(AObbInfo* obbInfo); + +/** + * Get the version of an OBB file. + */ +int32_t AObbInfo_getVersion(AObbInfo* obbInfo); + +/** + * Get the flags of an OBB file. + */ +int32_t AObbInfo_getFlags(AObbInfo* obbInfo); + +#ifdef __cplusplus +}; +#endif + +#endif // ANDROID_OBB_H diff --git a/native/include/android/storage_manager.h b/native/include/android/storage_manager.h index bbed8a4..6f925c1 100644 --- a/native/include/android/storage_manager.h +++ b/native/include/android/storage_manager.h @@ -37,17 +37,22 @@ AStorageManager* AStorageManager_new(); void AStorageManager_delete(AStorageManager* mgr); /** - * Callback to call when requested OBB is complete. + * Callback function for asynchronous calls made on OBB files. */ -void AStorageManager_setObbCallback(AStorageManager* mgr, void* cb); +typedef void (*AStorageManager_obbCallbackFunc)(const char* filename, const char* state, void* data); /** - * Attempts to mount an OBB file. + * Callback to call when requested asynchronous OBB operation is complete. + */ +void AStorageManager_setObbCallback(AStorageManager* mgr, AStorageManager_obbCallbackFunc cb, void* data); + +/** + * Attempts to mount an OBB file. This is an asynchronous operation. */ void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key); /** - * Attempts to unmount an OBB file. + * Attempts to unmount an OBB file. This is an asynchronous operation. */ void AStorageManager_unmountObb(AStorageManager* mgr, const char* filename, const int force); @@ -66,4 +71,4 @@ const char* AStorageManager_getMountedObbPath(AStorageManager* mgr, const char* }; #endif -#endif // ANDROID_PACKAGE_MANAGER_H +#endif // ANDROID_STORAGE_MANAGER_H diff --git a/opengl/tests/testFramerate/Android.mk b/opengl/tests/testFramerate/Android.mk new file mode 100644 index 0000000..500abf3 --- /dev/null +++ b/opengl/tests/testFramerate/Android.mk @@ -0,0 +1,19 @@ +######################################################################### +# Test framerate and look for hiccups +######################################################################### + +TOP_LOCAL_PATH:= $(call my-dir) + +# Build activity + + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := TestFramerate + +include $(BUILD_PACKAGE) diff --git a/opengl/tests/testFramerate/AndroidManifest.xml b/opengl/tests/testFramerate/AndroidManifest.xml new file mode 100644 index 0000000..e04342c --- /dev/null +++ b/opengl/tests/testFramerate/AndroidManifest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2009, 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. +*/ +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.testframerate"> + <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" /> + + <application + android:label="@string/testFramerate_activity"> + <activity android:name="TestFramerateActivity" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" + android:launchMode="singleTask" + android:configChanges="orientation|keyboardHidden"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/opengl/tests/testFramerate/res/values/strings.xml b/opengl/tests/testFramerate/res/values/strings.xml new file mode 100644 index 0000000..e6b3088 --- /dev/null +++ b/opengl/tests/testFramerate/res/values/strings.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This file contains resource definitions for displayed strings, allowing + them to be changed based on the locale and options. --> + +<resources> + <!-- Simple strings. --> + <string name="testFramerate_activity">TestFramerate</string> + +</resources> + diff --git a/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java new file mode 100644 index 0000000..cbe279b --- /dev/null +++ b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateActivity.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 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. + */ + +package com.android.testframerate; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.WindowManager; + +import java.io.File; + + +public class TestFramerateActivity extends Activity { + + TestFramerateView mView; + + @Override protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + mView = new TestFramerateView(getApplication()); + setContentView(mView); + mView.setFocusableInTouchMode(true); + } + + @Override protected void onPause() { + super.onPause(); + mView.onPause(); + } + + @Override protected void onResume() { + super.onResume(); + mView.onResume(); + } +} diff --git a/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java new file mode 100644 index 0000000..f3fb5de --- /dev/null +++ b/opengl/tests/testFramerate/src/com/android/testframerate/TestFramerateView.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2009 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.testframerate; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.os.SystemProperties; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.opengles.GL10; + +import android.opengl.GLES20; + +class TestFramerateView extends GLSurfaceView { + private static String TAG = "TestFramerateView"; + + public TestFramerateView(Context context) { + super(context); + setEGLContextClientVersion(2); + setRenderer(new Renderer()); + } + + private long mLastTime_us = 0; + private long mNumShortFramesElapsed = 0; + private void registerTime(long now_us) { + long longFrameTime_ms = Integer.parseInt(SystemProperties.get("debug.longframe_ms", "16")); + long elapsedTime_us = now_us - mLastTime_us; + float fps = 1000000.f / elapsedTime_us; + if (mLastTime_us > 0 && elapsedTime_us > longFrameTime_ms*1000) { + Log.v(TAG, "Long frame: " + elapsedTime_us/1000.f + " ms (" + fps + " fps)"); + if (mNumShortFramesElapsed > 0) { + Log.v(TAG, " Short frames since last long frame: " + mNumShortFramesElapsed); + mNumShortFramesElapsed = 0; + } + } else { + ++mNumShortFramesElapsed; + } + + mLastTime_us = now_us; + } + + private class Renderer implements GLSurfaceView.Renderer { + public Renderer() { + } + + + public void onDrawFrame(GL10 gl) { + long now_us = System.nanoTime() / 1000; + registerTime(now_us); + + float red = (now_us % 1000000) / 1000000.f; + float green = (now_us % 2000000) / 2000000.f; + float blue = (now_us % 3000000) / 3000000.f; + GLES20.glClearColor(red, green, blue, 1.0f); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + } + + public void onSurfaceChanged(GL10 gl, int width, int height) { + GLES20.glViewport(0, 0, width, height); + } + + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + } + + } +} diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index f08bd3c..eb86277 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -156,7 +156,12 @@ public class DefaultContainerService extends IntentService { } public ObbInfo getObbInfo(String filename) { - return ObbScanner.getObbInfo(filename); + try { + return ObbScanner.getObbInfo(filename); + } catch (IOException e) { + Log.d(TAG, "Couldn't get OBB info", e); + return null; + } } }; diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f1f31cf..6057023 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -32,6 +32,7 @@ <activity android:name=".recent.RecentApplicationsActivity" android:theme="@android:style/Theme.NoTitleBar" android:excludeFromRecents="true" + android:launchMode="singleInstance" android:exported="true"> </activity> diff --git a/packages/SystemUI/res/anim/navigation_in.xml b/packages/SystemUI/res/anim/navigation_in.xml new file mode 100644 index 0000000..630fd72 --- /dev/null +++ b/packages/SystemUI/res/anim/navigation_in.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + > + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" + /> +</set> diff --git a/packages/SystemUI/res/anim/navigation_out.xml b/packages/SystemUI/res/anim/navigation_out.xml new file mode 100644 index 0000000..4717e47 --- /dev/null +++ b/packages/SystemUI/res/anim/navigation_out.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + > + <alpha android:toAlpha="0.0" android:fromAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" + /> +</set> diff --git a/packages/SystemUI/res/anim/recent_app_enter.xml b/packages/SystemUI/res/anim/recent_app_enter.xml new file mode 100644 index 0000000..4947eee --- /dev/null +++ b/packages/SystemUI/res/anim/recent_app_enter.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2009, 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. +*/ +--> + +<!-- Special window zoom animation: this is the element that enters the screen, + it starts at 200% and scales down. Goes with zoom_exit.xml. --> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/decelerate_interpolator"> + <scale android:fromXScale="0.25" android:toXScale="1.0" + android:fromYScale="0.25" android:toYScale="1.0" + android:pivotX="0%p" android:pivotY="0%p" + android:duration="500" /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="500"/> +</set> diff --git a/packages/SystemUI/res/anim/recent_app_leave.xml b/packages/SystemUI/res/anim/recent_app_leave.xml new file mode 100644 index 0000000..3d83988 --- /dev/null +++ b/packages/SystemUI/res/anim/recent_app_leave.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2009, 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. +*/ +--> + +<!-- Special window zoom animation: this is the element that enters the screen, + it starts at 200% and scales down. Goes with zoom_exit.xml. --> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/decelerate_interpolator"> + <scale android:fromXScale="1.0" android:toXScale="0.25" + android:fromYScale="1.0" android:toYScale="0.25" + android:pivotX="0%p" android:pivotY="0%p" + android:duration="500" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="500"/> +</set> diff --git a/packages/SystemUI/res/anim/system_in.xml b/packages/SystemUI/res/anim/system_in.xml new file mode 100644 index 0000000..630fd72 --- /dev/null +++ b/packages/SystemUI/res/anim/system_in.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + > + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" + /> +</set> diff --git a/packages/SystemUI/res/anim/system_out.xml b/packages/SystemUI/res/anim/system_out.xml new file mode 100644 index 0000000..4717e47 --- /dev/null +++ b/packages/SystemUI/res/anim/system_out.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + > + <alpha android:toAlpha="0.0" android:fromAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" + /> +</set> diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_veto_normal.png b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_normal.png Binary files differindex f9a7cc2..3b7c9c7 100644 --- a/packages/SystemUI/res/drawable-mdpi/status_bar_veto_normal.png +++ b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_normal.png diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png Binary files differindex 4461ac8..3b7c9c7 100644 --- a/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png +++ b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png diff --git a/packages/SystemUI/res/drawable/recent_overlay.png b/packages/SystemUI/res/drawable/recent_overlay.png Binary files differnew file mode 100644 index 0000000..4dfa3d9 --- /dev/null +++ b/packages/SystemUI/res/drawable/recent_overlay.png diff --git a/packages/SystemUI/res/drawable/recent_rez_border.png b/packages/SystemUI/res/drawable/recent_rez_border.png Binary files differnew file mode 100644 index 0000000..ad025f5 --- /dev/null +++ b/packages/SystemUI/res/drawable/recent_rez_border.png diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml index 5741a66..6aee011 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml @@ -119,46 +119,50 @@ android:layout_centerInParent="true" /> - <com.android.systemui.statusbar.KeyButtonView android:id="@+id/menu" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_toLeftOf="@+id/recent" - android:src="@drawable/ic_sysbar_menu" - android:background="@drawable/ic_sysbar_icon_bg" - android:paddingLeft="4dip" - android:paddingRight="4dip" - systemui:keyCode="82" - /> - <ImageButton android:id="@+id/recent" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_toLeftOf="@+id/home" - android:src="@drawable/ic_sysbar_recent" - android:background="@drawable/ic_sysbar_icon_bg" - android:paddingLeft="4dip" - android:paddingRight="4dip" - android:onClick="recentButtonClicked" - /> - <com.android.systemui.statusbar.KeyButtonView android:id="@+id/home" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_toLeftOf="@+id/back" - android:paddingLeft="4dip" - android:paddingRight="4dip" - android:src="@drawable/ic_sysbar_home" - android:background="@drawable/ic_sysbar_icon_bg" - systemui:keyCode="3" - /> - <com.android.systemui.statusbar.KeyButtonView android:id="@+id/back" + <LinearLayout + android:id="@+id/navigationArea" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentRight="true" - android:paddingLeft="4dip" - android:paddingRight="4dip" - android:src="@drawable/ic_sysbar_back" - android:background="@drawable/ic_sysbar_icon_bg" - systemui:keyCode="4" - /> + android:orientation="horizontal" + > + <com.android.systemui.statusbar.KeyButtonView android:id="@+id/menu" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:src="@drawable/ic_sysbar_menu" + android:background="@drawable/ic_sysbar_icon_bg" + android:paddingLeft="4dip" + android:paddingRight="4dip" + systemui:keyCode="82" + /> + <ImageButton android:id="@+id/recent" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:src="@drawable/ic_sysbar_recent" + android:background="@drawable/ic_sysbar_icon_bg" + android:paddingLeft="4dip" + android:paddingRight="4dip" + android:onClick="recentButtonClicked" + /> + <com.android.systemui.statusbar.KeyButtonView android:id="@+id/home" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:paddingLeft="4dip" + android:paddingRight="4dip" + android:src="@drawable/ic_sysbar_home" + android:background="@drawable/ic_sysbar_icon_bg" + systemui:keyCode="3" + /> + <com.android.systemui.statusbar.KeyButtonView android:id="@+id/back" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:paddingLeft="4dip" + android:paddingRight="4dip" + android:src="@drawable/ic_sysbar_back" + android:background="@drawable/ic_sysbar_icon_bg" + systemui:keyCode="4" + /> + </LinearLayout> </RelativeLayout> <!-- It's curtains for you. --> diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml b/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml index fa492d0..049a1cc 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml @@ -8,12 +8,14 @@ <ImageButton android:id="@+id/veto" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:src="@drawable/status_bar_veto" + android:scaleType="center" android:background="@null" - android:padding="2dip" + android:paddingLeft="16dip" + android:paddingRight="16dip" /> <com.android.systemui.statusbar.LatestItemView android:id="@+id/content" diff --git a/packages/SystemUI/res/layout/recents_detail_view.xml b/packages/SystemUI/res/layout/recents_detail_view.xml new file mode 100644 index 0000000..879d0f2 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_detail_view.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <!-- Application Title --> + <TextView android:id="@+id/app_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:singleLine="true"/> + + <!-- Application Details --> + <TextView + android:id="@+id/app_description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall"/> + +</LinearLayout> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java index 9cc24be..bf24a1f 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java @@ -20,7 +20,9 @@ package com.android.systemui.recent; import com.android.systemui.R; import com.android.ex.carousel.CarouselView; +import com.android.ex.carousel.CarouselViewHelper; import com.android.ex.carousel.CarouselRS.CarouselCallback; +import com.android.ex.carousel.CarouselViewHelper.DetailTextureParameters; import java.util.ArrayList; import java.util.List; @@ -38,37 +40,104 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.PorterDuffXfermode; +import android.graphics.PorterDuff; import android.graphics.Bitmap.Config; import android.graphics.drawable.Drawable; import android.graphics.PixelFormat; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.View; +import android.view.View.MeasureSpec; +import android.widget.TextView; public class RecentApplicationsActivity extends Activity { private static final String TAG = "RecentApplicationsActivity"; - private static boolean DBG = true; + private static boolean DBG = false; private static final int CARD_SLOTS = 56; private static final int VISIBLE_SLOTS = 7; private static final int MAX_TASKS = VISIBLE_SLOTS * 2; + + // TODO: these should be configurable + private static final int DETAIL_TEXTURE_MAX_WIDTH = 200; + private static final int DETAIL_TEXTURE_MAX_HEIGHT = 80; + private static final int TEXTURE_WIDTH = 256; + private static final int TEXTURE_HEIGHT = 256; + private ActivityManager mActivityManager; private List<RunningTaskInfo> mRunningTaskList; private boolean mPortraitMode = true; private ArrayList<ActivityDescription> mActivityDescriptions = new ArrayList<ActivityDescription>(); private CarouselView mCarouselView; + private LocalCarouselViewHelper mHelper; private View mNoRecentsView; - private Bitmap mBlankBitmap = Bitmap.createBitmap( - new int[] {0xff808080, 0xffffffff, 0xff808080, 0xffffffff}, 2, 2, Config.RGB_565); + private Bitmap mLoadingBitmap; + private Bitmap mRecentOverlay; + private boolean mHidden = false; + private boolean mHiding = false; + private DetailInfo mDetailInfo; + + /** + * This class is a container for all items associated with the DetailView we'll + * be drawing to a bitmap and sending to Carousel. + * + */ + static final class DetailInfo { + public DetailInfo(View _view, TextView _title, TextView _desc) { + view = _view; + title = _title; + description = _desc; + } + + /** + * Draws view into the given bitmap, if provided + * @param bitmap + */ + public Bitmap draw(Bitmap bitmap) { + resizeView(view, DETAIL_TEXTURE_MAX_WIDTH, DETAIL_TEXTURE_MAX_HEIGHT); + int desiredWidth = view.getWidth(); + int desiredHeight = view.getHeight(); + if (bitmap == null || desiredWidth != bitmap.getWidth() + || desiredHeight != bitmap.getHeight()) { + bitmap = Bitmap.createBitmap(desiredWidth, desiredHeight, Config.ARGB_8888); + } + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + return bitmap; + } + + /** + * Force a layout pass on the given view. + */ + private void resizeView(View view, int maxWidth, int maxHeight) { + int widthSpec = MeasureSpec.getMode(MeasureSpec.AT_MOST) + | MeasureSpec.getSize(maxWidth); + int heightSpec = MeasureSpec.getMode(MeasureSpec.AT_MOST) + | MeasureSpec.getSize(maxHeight); + view.measure(widthSpec, heightSpec); + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + Log.v(TAG, "RESIZED VIEW: " + view.getWidth() + ", " + view.getHeight()); + } + + public View view; + public TextView title; + public TextView description; + } static class ActivityDescription { int id; Bitmap thumbnail; // generated by Activity.onCreateThumbnail() Drawable icon; // application package icon String label; // application package label - String description; // generated by Activity.onCreateDescription() + CharSequence description; // generated by Activity.onCreateDescription() Intent intent; // launch intent for application Matrix matrix; // arbitrary rotation matrix to correct orientation int position; // position in list @@ -106,14 +175,17 @@ public class RecentApplicationsActivity extends Activity { return null; } - final CarouselCallback mCarouselCallback = new CarouselCallback() { - - public void onAnimationFinished() { + private class LocalCarouselViewHelper extends CarouselViewHelper { + private Paint mPaint = new Paint(); + private DetailTextureParameters mDetailParams = new DetailTextureParameters(10.0f, 20.0f); + public LocalCarouselViewHelper(Context context) { + super(context); } - public void onAnimationStarted() { - + @Override + public DetailTextureParameters getDetailTextureParameters(int id) { + return mDetailParams; } public void onCardSelected(int n) { @@ -125,7 +197,7 @@ public class RecentApplicationsActivity extends Activity { try { if (DBG) Log.v(TAG, "Starting intent " + item.intent); startActivity(item.intent); - //overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit); + overridePendingTransition(R.anim.recent_app_enter, R.anim.recent_app_leave); } catch (ActivityNotFoundException e) { if (DBG) Log.w("Recent", "Unable to launch recent task", e); } @@ -134,48 +206,88 @@ public class RecentApplicationsActivity extends Activity { } } - public void onInvalidateTexture(int n) { - - } - - public void onRequestGeometry(int n) { - - } - - public void onInvalidateGeometry(int n) { - + @Override + public Bitmap getTexture(final int id) { + if (DBG) Log.v(TAG, "onRequestTexture(" + id + ")"); + ActivityDescription info; + synchronized(mActivityDescriptions) { + info = mActivityDescriptions.get(id); + } + Bitmap bitmap = null; + if (info != null) { + bitmap = compositeBitmap(info); + } + return bitmap; } - public void onRequestTexture(final int n) { - if (DBG) Log.v(TAG, "onRequestTexture(" + n + ")"); + @Override + public Bitmap getDetailTexture(int n) { + Bitmap bitmap = null; if (n < mActivityDescriptions.size()) { - mCarouselView.post(new Runnable() { - public void run() { - ActivityDescription info = mActivityDescriptions.get(n); - if (info != null) { - if (DBG) Log.v(TAG, "FOUND ACTIVITY THUMBNAIL " + info.thumbnail); - Bitmap bitmap = info.thumbnail == null ? mBlankBitmap : info.thumbnail; - mCarouselView.setTextureForItem(n, bitmap); - } else { - if (DBG) Log.v(TAG, "FAILED TO GET ACTIVITY THUMBNAIL FOR ITEM " + n); - } - } - }); + ActivityDescription item = mActivityDescriptions.get(n); + mDetailInfo.title.setText(item.label); + mDetailInfo.description.setText(item.description); + bitmap = mDetailInfo.draw(null); } + return bitmap; } + }; - public void onInvalidateDetailTexture(int n) { - + private Bitmap compositeBitmap(ActivityDescription info) { + final int targetWidth = TEXTURE_WIDTH; + final int targetHeight = TEXTURE_HEIGHT; + final int border = 3; // inset along the edge for thumnnail content + final int overlap = 1; // how many pixels of overlap between border and thumbnail + final Resources res = getResources(); + if (mRecentOverlay == null) { + mRecentOverlay = BitmapFactory.decodeResource(res, R.drawable.recent_overlay); } - public void onRequestDetailTexture(int n) { - + // Create a bitmap of the proper size/format and set the canvas to draw to it + final Bitmap result = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(result); + canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG)); + Paint paint = new Paint(); + paint.setFilterBitmap(false); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + canvas.save(); + if (info.thumbnail != null) { + // Draw the thumbnail + int sourceWidth = targetWidth - 2 * (border - overlap); + int sourceHeight = targetHeight - 2 * (border - overlap); + final float scaleX = (float) sourceWidth / info.thumbnail.getWidth(); + final float scaleY = (float) sourceHeight / info.thumbnail.getHeight(); + canvas.translate(border * 0.5f, border * 0.5f); + canvas.scale(scaleX, scaleY); + canvas.drawBitmap(info.thumbnail, 0, 0, paint); + } else { + // Draw the Loading bitmap placeholder, TODO: Remove when RS handles blending + final float scaleX = (float) targetWidth / mLoadingBitmap.getWidth(); + final float scaleY = (float) targetHeight / mLoadingBitmap.getHeight(); + canvas.scale(scaleX, scaleY); + canvas.drawBitmap(mLoadingBitmap, 0, 0, paint); } - - public void onReportFirstCardPosition(int n) { - + canvas.restore(); + + // Draw overlay + canvas.save(); + final float scaleOverlayX = (float) targetWidth / mRecentOverlay.getWidth(); + final float scaleOverlayY = (float) targetHeight / mRecentOverlay.getHeight(); + canvas.scale(scaleOverlayX, scaleOverlayY); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); + canvas.drawBitmap(mRecentOverlay, 0, 0, paint); + canvas.restore(); + + // Draw icon + if (info.icon != null) { + canvas.save(); + info.icon.draw(canvas); + canvas.restore(); } - }; + + return result; + } private final IThumbnailReceiver mThumbnailReceiver = new IThumbnailReceiver.Stub() { @@ -192,6 +304,7 @@ public class RecentApplicationsActivity extends Activity { ActivityDescription info = findActivityDescription(id); if (info != null) { info.thumbnail = bitmap; + info.description = description; final int thumbWidth = bitmap.getWidth(); final int thumbHeight = bitmap.getHeight(); if ((mPortraitMode && thumbWidth > thumbHeight) @@ -202,13 +315,34 @@ public class RecentApplicationsActivity extends Activity { } else { info.matrix = null; } - mCarouselView.setTextureForItem(info.position, info.thumbnail); + mCarouselView.setTextureForItem(info.position, compositeBitmap(info)); } else { if (DBG) Log.v(TAG, "Can't find view for id " + id); } } }; + /** + * We never really finish() RecentApplicationsActivity, since we don't want to + * get destroyed and pay the start-up cost to restart it. + */ + @Override + public void finish() { + moveTaskToBack(true); + } + + @Override + protected void onNewIntent(Intent intent) { + mHidden = !mHidden; + if (mHidden) { + mHiding = true; + moveTaskToBack(true); + } else { + mHiding = false; + } + super.onNewIntent(intent); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -218,25 +352,35 @@ public class RecentApplicationsActivity extends Activity { getWindow().getDecorView().setBackgroundColor(0x80000000); setContentView(R.layout.recent_apps_activity); - mCarouselView = (CarouselView)findViewById(R.id.carousel); - mNoRecentsView = (View) findViewById(R.id.no_applications_message); - //mCarouselView = new CarouselView(this); - //setContentView(mCarouselView); - mCarouselView.setSlotCount(CARD_SLOTS); - mCarouselView.setVisibleSlots(VISIBLE_SLOTS); - mCarouselView.createCards(1); - mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS)); - mCarouselView.setDefaultBitmap(mBlankBitmap); - mCarouselView.setLoadingBitmap(mBlankBitmap); - mCarouselView.setCallback(mCarouselCallback); - mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT); - - mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); - mPortraitMode = decorView.getHeight() > decorView.getWidth(); - refresh(); + if (mCarouselView == null) { + mLoadingBitmap = BitmapFactory.decodeResource(res, R.drawable.recent_rez_border); + mCarouselView = (CarouselView)findViewById(R.id.carousel); + mHelper = new LocalCarouselViewHelper(this); + mHelper.setCarouselView(mCarouselView); + + mCarouselView.setSlotCount(CARD_SLOTS); + mCarouselView.setVisibleSlots(VISIBLE_SLOTS); + mCarouselView.createCards(0); + mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS)); + mCarouselView.setDefaultBitmap(mLoadingBitmap); + mCarouselView.setLoadingBitmap(mLoadingBitmap); + mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT); + + mNoRecentsView = (View) findViewById(R.id.no_applications_message); + + mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + mPortraitMode = decorView.getHeight() > decorView.getWidth(); + + // Load detail view which will be used to render text + View detail = getLayoutInflater().inflate(R.layout.recents_detail_view, null); + TextView title = (TextView) detail.findViewById(R.id.app_title); + TextView description = (TextView) detail.findViewById(R.id.app_description); + mDetailInfo = new DetailInfo(detail, title, description); + refresh(); + } } @Override @@ -264,7 +408,7 @@ public class RecentApplicationsActivity extends Activity { ActivityDescription desc = findActivityDescription(r.id); if (desc != null) { desc.thumbnail = r.thumbnail; - desc.label = r.topActivity.flattenToShortString(); + desc.description = r.description; if ((mPortraitMode && thumbWidth > thumbHeight) || (!mPortraitMode && thumbWidth < thumbHeight)) { Matrix matrix = new Matrix(); @@ -336,17 +480,34 @@ public class RecentApplicationsActivity extends Activity { } } - private void refresh() { - updateRecentTasks(); - updateRunningTasks(); - if (mActivityDescriptions.size() == 0) { - // show "No Recent Takss" - mNoRecentsView.setVisibility(View.VISIBLE); - mCarouselView.setVisibility(View.GONE); - } else { + private final Runnable mRefreshRunnable = new Runnable() { + public void run() { + updateRecentTasks(); + updateRunningTasks(); + showCarousel(mActivityDescriptions.size() > 0); + } + }; + + private void showCarousel(boolean show) { + if (show) { + // Make carousel visible mNoRecentsView.setVisibility(View.GONE); mCarouselView.setVisibility(View.VISIBLE); mCarouselView.createCards(mActivityDescriptions.size()); + } else { + // show "No Recent Tasks" + mNoRecentsView.setVisibility(View.VISIBLE); + mCarouselView.setVisibility(View.GONE); + } + } + + private void refresh() { + if (!mHiding && mCarouselView != null) { + // Don't update the view now. Instead, post a request so it happens next time + // we reach the looper after a delay. This way we can fold multiple refreshes + // into just the latest. + mCarouselView.removeCallbacks(mRefreshRunnable); + mCarouselView.postDelayed(mRefreshRunnable, 50); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CarrierLabel.java b/packages/SystemUI/src/com/android/systemui/statusbar/CarrierLabel.java index d89d093..31b78b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CarrierLabel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CarrierLabel.java @@ -99,7 +99,7 @@ public class CarrierLabel extends TextView { } if (showSpn && spn != null) { if (something) { - str.append(' '); + str.append('\n'); } str.append(spn); something = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 3ef12f9..81091e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -175,8 +175,10 @@ public class CommandQueue extends IStatusBar.Stub { break; } case OP_REMOVE_ICON: - mList.removeIcon(index); - mCallbacks.removeIcon(mList.getSlot(index), index, viewIndex); + if (mList.getIcon(index) != null) { + mList.removeIcon(index); + mCallbacks.removeIcon(mList.getSlot(index), index, viewIndex); + } break; } break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 45abe93..7e8a5c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -117,7 +117,7 @@ public class NotificationData { public boolean hasClearableItems() { for (Entry e : mEntries) { if (e.expanded != null) { // the view successfully inflated - if ((e.notification.notification.flags & Notification.FLAG_NO_CLEAR) == 0) { + if (e.notification.isClearable()) { return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java index 0309430..e0019b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java @@ -18,9 +18,8 @@ package com.android.systemui.statusbar.policy; import android.app.StatusBarManager; import android.app.AlertDialog; -import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothPbap; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -319,9 +318,6 @@ public class StatusBarPolicy { private boolean mVolumeVisible; // bluetooth device status - private int mBluetoothHeadsetState; - private boolean mBluetoothA2dpConnected; - private int mBluetoothPbapState; private boolean mBluetoothEnabled; // wifi @@ -369,9 +365,7 @@ public class StatusBarPolicy { onBatteryOkay(intent); } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) || - action.equals(BluetoothHeadset.ACTION_STATE_CHANGED) || - action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED) || - action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { + action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { updateBluetooth(intent); } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) || @@ -454,9 +448,6 @@ public class StatusBarPolicy { } else { mBluetoothEnabled = false; } - mBluetoothA2dpConnected = false; - mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED; - mBluetoothPbapState = BluetoothPbap.STATE_DISCONNECTED; mService.setIconVisibility("bluetooth", mBluetoothEnabled); // Gps status @@ -490,9 +481,7 @@ public class StatusBarPolicy { filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); - filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); - filter.addAction(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); + filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); @@ -1064,28 +1053,16 @@ public class StatusBarPolicy { if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); mBluetoothEnabled = state == BluetoothAdapter.STATE_ON; - } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { - mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, - BluetoothHeadset.STATE_ERROR); - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - if (a2dp.getConnectedSinks().size() != 0) { - mBluetoothA2dpConnected = true; - } else { - mBluetoothA2dpConnected = false; + } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.STATE_DISCONNECTED); + if (state == BluetoothAdapter.STATE_CONNECTED) { + iconId = R.drawable.stat_sys_data_bluetooth_connected; } - } else if (action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { - mBluetoothPbapState = intent.getIntExtra(BluetoothPbap.PBAP_STATE, - BluetoothPbap.STATE_DISCONNECTED); } else { return; } - if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED || mBluetoothA2dpConnected || - mBluetoothPbapState == BluetoothPbap.STATE_CONNECTED) { - iconId = R.drawable.stat_sys_data_bluetooth_connected; - } - mService.setIcon("bluetooth", iconId, 0); mService.setIconVisibility("bluetooth", mBluetoothEnabled); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java index 49337fd..0e26f52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java @@ -78,6 +78,7 @@ public class TabletStatusBarService extends StatusBarService { NotificationIconArea mNotificationIconArea; View mNotificationButtons; View mSystemInfo; + View mNavigationArea; NotificationPanel mNotificationPanel; SystemPanel mSystemPanel; @@ -98,7 +99,6 @@ public class TabletStatusBarService extends StatusBarService { TabletTicker mTicker; View mTickerView; boolean mTicking; - boolean mExpandedVisible; // for disabling the status bar int mDisabled = 0; @@ -203,6 +203,12 @@ public class TabletStatusBarService extends StatusBarService { mSignalMeter = (ImageView) sb.findViewById(R.id.signal); mSignalIcon = (ImageView) sb.findViewById(R.id.signal_icon); + // The navigation buttons + mNavigationArea = sb.findViewById(R.id.navigationArea); + + // set the initial view visibility + setAreThereNotifications(); + // Add the windows addPanelWindows(); @@ -224,24 +230,26 @@ public class TabletStatusBarService extends StatusBarService { switch (m.what) { case MSG_OPEN_NOTIFICATION_PANEL: if (DEBUG) Slog.d(TAG, "opening notifications panel"); - mDoNotDisturbButton.setText(mNotificationsOn - ? R.string.status_bar_do_not_disturb_button - : R.string.status_bar_please_disturb_button); - mNotificationPanel.setVisibility(View.VISIBLE); - mNotificationIconArea.setAnimation(loadAnim(R.anim.notification_icons_out)); - mNotificationIconArea.setVisibility(View.GONE); - mNotificationButtons.setAnimation(loadAnim(R.anim.notification_icons_in)); - mNotificationButtons.setVisibility(View.VISIBLE); - mExpandedVisible = true; + if (mNotificationPanel.getVisibility() == View.GONE) { + mDoNotDisturbButton.setText(mNotificationsOn + ? R.string.status_bar_do_not_disturb_button + : R.string.status_bar_please_disturb_button); + mNotificationPanel.setVisibility(View.VISIBLE); + setViewVisibility(mNotificationIconArea, View.GONE, + R.anim.notification_icons_out); + setViewVisibility(mNotificationButtons, View.VISIBLE, + R.anim.notification_buttons_in); + } break; case MSG_CLOSE_NOTIFICATION_PANEL: if (DEBUG) Slog.d(TAG, "closing notifications panel"); - mNotificationPanel.setVisibility(View.GONE); - mNotificationIconArea.setAnimation(loadAnim(R.anim.notification_icons_in)); - mNotificationIconArea.setVisibility(View.VISIBLE); - mNotificationButtons.setAnimation(loadAnim(R.anim.notification_buttons_out)); - mNotificationButtons.setVisibility(View.GONE); - mExpandedVisible = false; + if (mNotificationPanel.getVisibility() == View.VISIBLE) { + mNotificationPanel.setVisibility(View.GONE); + setViewVisibility(mNotificationIconArea, View.VISIBLE, + R.anim.notification_icons_in); + setViewVisibility(mNotificationButtons, View.GONE, + R.anim.notification_buttons_out); + } break; case MSG_OPEN_SYSTEM_PANEL: if (DEBUG) Slog.d(TAG, "opening system panel"); @@ -313,6 +321,8 @@ public class TabletStatusBarService extends StatusBarService { } else { tick(notification); } + + setAreThereNotifications(); } public void updateNotification(IBinder key, StatusBarNotification notification) { @@ -382,19 +392,25 @@ public class TabletStatusBarService extends StatusBarService { addNotificationViews(key, notification); } // TODO: ticker; immersive mode + + setAreThereNotifications(); } public void removeNotification(IBinder key) { if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO"); removeNotificationViews(key); + setAreThereNotifications(); } public void disable(int state) { - /* - final int old = mDisabled; - final int diff = state ^ old; + int old = mDisabled; + int diff = state ^ old; + Slog.d(TAG, "disable... old=0x" + Integer.toHexString(old) + + " diff=0x" + Integer.toHexString(diff) + + " state=0x" + Integer.toHexString(state)); mDisabled = state; + // act accordingly if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { Slog.d(TAG, "DISABLE_EXPAND: yes"); @@ -404,60 +420,41 @@ public class TabletStatusBarService extends StatusBarService { if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); - if (mTicking) { - mTicker.halt(); - } else { - mNotificationIconArea.setVisibility(View.INVISIBLE); - } + setViewVisibility(mNotificationTrigger, View.GONE, + R.anim.notification_icons_out); + setViewVisibility(mNotificationIconArea, View.GONE, + R.anim.notification_icons_out); + mTicker.halt(); } else { Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); - if (!mExpandedVisible) { - mNotificationIconArea.setVisibility(View.VISIBLE); - } + setViewVisibility(mNotificationTrigger, View.VISIBLE, + R.anim.notification_icons_in); + setViewVisibility(mNotificationIconArea, View.VISIBLE, + R.anim.notification_icons_in); } } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: yes"); + if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { mTicker.halt(); } } - */ - } - - void performDisableActions(int net) { - /* - int old = mDisabled; - int diff = net ^ old; - mDisabled = net; - - // act accordingly - if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { - if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { - Slog.d(TAG, "DISABLE_EXPAND: yes"); - animateCollapse(); + if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { + if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { + Slog.d(TAG, "DISABLE_SYSTEM_INFO: yes"); + setViewVisibility(mSystemInfo, View.GONE, R.anim.navigation_out); + } else { + Slog.d(TAG, "DISABLE_SYSTEM_INFO: no"); + setViewVisibility(mSystemInfo, View.VISIBLE, R.anim.navigation_in); } } - if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); - if (mTicking) { - mNotificationIconArea.setVisibility(View.INVISIBLE); - mTicker.halt(); - } else { - mNotificationIconArea.setVisibility(View.INVISIBLE); - } + if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) { + if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) { + Slog.d(TAG, "DISABLE_NAVIGATION: yes"); + setViewVisibility(mNavigationArea, View.GONE, R.anim.navigation_out); } else { - Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); - if (!mExpandedVisible) { - mNotificationIconArea.setVisibility(View.VISIBLE); - } - } - } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - mTicker.halt(); + Slog.d(TAG, "DISABLE_NAVIGATION: no"); + setViewVisibility(mNavigationArea, View.VISIBLE, R.anim.navigation_in); } } - */ } private boolean hasTicker(Notification n) { @@ -497,35 +494,70 @@ public class TabletStatusBarService extends StatusBarService { public void setLightsOn(boolean on) { if (on) { - mCurtains.setAnimation(loadAnim(R.anim.lights_out_out)); - mCurtains.setVisibility(View.GONE); - mBarContents.setAnimation(loadAnim(R.anim.status_bar_in)); - mBarContents.setVisibility(View.VISIBLE); + setViewVisibility(mCurtains, View.GONE, R.anim.lights_out_out); + setViewVisibility(mBarContents, View.VISIBLE, R.anim.status_bar_in); } else { animateCollapse(); - mCurtains.setAnimation(loadAnim(R.anim.lights_out_in)); - mCurtains.setVisibility(View.VISIBLE); - mBarContents.setAnimation(loadAnim(R.anim.status_bar_out)); - mBarContents.setVisibility(View.GONE); + setViewVisibility(mCurtains, View.VISIBLE, R.anim.lights_out_in); + setViewVisibility(mBarContents, View.GONE, R.anim.status_bar_out); } } + private void setAreThereNotifications() { + final boolean hasClearable = mNotns.hasClearableItems(); + + //Slog.d(TAG, "setAreThereNotifications hasClerable=" + hasClearable); + + // Show or hide the "Clear all" button. Note that we don't do an animation + // if it's not on screen, so that if someone opens the bar right then they + // don't see the animation in progress. + // (no ongoing notifications are clearable) + if (hasClearable) { + if (mNotificationButtons.getVisibility() == View.VISIBLE) { + setViewVisibility(mClearButton, View.VISIBLE, R.anim.notification_buttons_in); + } else { + mClearButton.setVisibility(View.VISIBLE); + } + } else { + if (mNotificationButtons.getVisibility() == View.VISIBLE) { + setViewVisibility(mClearButton, View.GONE, R.anim.notification_buttons_out); + } else { + mClearButton.setVisibility(View.GONE); + } + } + + /* + mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); + mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); + + if (ongoing || latest) { + mNoNotificationsTitle.setVisibility(View.GONE); + } else { + mNoNotificationsTitle.setVisibility(View.VISIBLE); + } + */ + } + public void notificationIconsClicked(View v) { if (DEBUG) Slog.d(TAG, "clicked notification icons"); - int msg = (mNotificationPanel.getVisibility() == View.GONE) - ? MSG_OPEN_NOTIFICATION_PANEL - : MSG_CLOSE_NOTIFICATION_PANEL; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); + if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { + int msg = (mNotificationPanel.getVisibility() == View.GONE) + ? MSG_OPEN_NOTIFICATION_PANEL + : MSG_CLOSE_NOTIFICATION_PANEL; + mHandler.removeMessages(msg); + mHandler.sendEmptyMessage(msg); + } } public void systemInfoClicked(View v) { if (DEBUG) Slog.d(TAG, "clicked system info"); - int msg = (mSystemPanel.getVisibility() == View.GONE) - ? MSG_OPEN_SYSTEM_PANEL - : MSG_CLOSE_SYSTEM_PANEL; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); + if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { + int msg = (mSystemPanel.getVisibility() == View.GONE) + ? MSG_OPEN_SYSTEM_PANEL + : MSG_CLOSE_SYSTEM_PANEL; + mHandler.removeMessages(msg); + mHandler.sendEmptyMessage(msg); + } } public void recentButtonClicked(View v) { @@ -690,10 +722,6 @@ public class TabletStatusBarService extends StatusBarService { } } - Animation loadAnim(int id) { - return AnimationUtils.loadAnimation((Context)this, id); - } - private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { StatusBarNotification sbn = entry.notification; RemoteViews remoteViews = sbn.notification.contentView; @@ -705,19 +733,23 @@ public class TabletStatusBarService extends StatusBarService { LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false); View vetoButton = row.findViewById(R.id.veto); - final String _pkg = sbn.pkg; - final String _tag = sbn.tag; - final int _id = sbn.id; - vetoButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - try { - mBarService.onNotificationClear(_pkg, _tag, _id); - } catch (RemoteException ex) { - // system process is dead if we're here. + if (entry.notification.isClearable()) { + final String _pkg = sbn.pkg; + final String _tag = sbn.tag; + final int _id = sbn.id; + vetoButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + try { + mBarService.onNotificationClear(_pkg, _tag, _id); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + // animateCollapse(); } -// animateCollapse(); - } - }); + }); + } else { + vetoButton.setVisibility(View.INVISIBLE); + } // bind the click event to the content area ViewGroup content = (ViewGroup)row.findViewById(R.id.content); @@ -802,6 +834,14 @@ public class TabletStatusBarService extends StatusBarService { return false; } } + + private void setViewVisibility(View v, int vis, int anim) { + if (v.getVisibility() != vis) { + //Slog.d(TAG, "setViewVisibility vis=" + (vis == View.VISIBLE) + " v=" + v); + v.setAnimation(AnimationUtils.loadAnimation((Context)this, anim)); + v.setVisibility(vis); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java index 7ac7919..3c3139f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java @@ -59,7 +59,6 @@ public class TabletTicker extends Handler { // TODO: Make this a configuration value. // 3 is enough to let us see most cases, but not get so far behind that it's annoying. - int mQueuePos = 0; mQueue = new StatusBarNotification[3]; } @@ -80,6 +79,18 @@ public class TabletTicker extends Handler { } } + public void halt() { + removeMessages(MSG_ADVANCE); + if (mCurrentView != null) { + final int N = mQueue.length; + for (int i=0; i<N; i++) { + mQueue[i] = null; + } + mQueuePos = 0; + sendEmptyMessage(MSG_ADVANCE); + } + } + public void handleMessage(Message msg) { switch (msg.what) { case MSG_ADVANCE: diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 8a732ed..97b8086 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -5332,6 +5332,15 @@ size_t AudioFlinger::EffectModule::removeHandle(const wp<EffectHandle>& handle) } } + // Release effect engine here so that it is done immediately. Otherwise it will be released + // by the destructor when the last strong reference on the this object is released which can + // happen after next process is called on this effect. + if (size == 0 && mEffectInterface != NULL) { + // release effect engine + EffectRelease(mEffectInterface); + mEffectInterface = NULL; + } + return size; } @@ -6145,21 +6154,36 @@ sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectFromId_l(int // Must be called with EffectChain::mLock locked void AudioFlinger::EffectChain::process_l() { + sp<ThreadBase> thread = mThread.promote(); + if (thread == 0) { + LOGW("process_l(): cannot promote mixer thread"); + return; + } + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + bool isGlobalSession = (mSessionId == AudioSystem::SESSION_OUTPUT_MIX) || + (mSessionId == AudioSystem::SESSION_OUTPUT_STAGE); + bool tracksOnSession = false; + if (!isGlobalSession) { + tracksOnSession = + playbackThread->hasAudioSession(mSessionId) & PlaybackThread::TRACK_SESSION; + } + size_t size = mEffects.size(); - for (size_t i = 0; i < size; i++) { - mEffects[i]->process(); + // do not process effect if no track is present in same audio session + if (isGlobalSession || tracksOnSession) { + for (size_t i = 0; i < size; i++) { + mEffects[i]->process(); + } } for (size_t i = 0; i < size; i++) { mEffects[i]->updateState(); } // if no track is active, input buffer must be cleared here as the mixer process // will not do it - if (mSessionId > 0 && activeTracks() == 0) { - sp<ThreadBase> thread = mThread.promote(); - if (thread != 0) { - size_t numSamples = thread->frameCount() * thread->channelCount(); - memset(mInBuffer, 0, numSamples * sizeof(int16_t)); - } + if (tracksOnSession && + activeTracks() == 0) { + size_t numSamples = playbackThread->frameCount() * playbackThread->channelCount(); + memset(mInBuffer, 0, numSamples * sizeof(int16_t)); } } diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp index f943a10..808c679 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -320,6 +320,7 @@ CameraService::Client::Client(const sp<CameraService>& cameraService, // Callback is disabled by default mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; mOrientation = 0; + mOrientationChanged = false; mPlayShutterSound = true; cameraService->setCameraBusy(cameraId); cameraService->loadSound(); @@ -491,6 +492,7 @@ status_t CameraService::Client::setPreviewDisplay(const sp<Surface>& surface) { // Force the destruction of any previous overlay sp<Overlay> dummy; mHardware->setOverlay(dummy); + mOverlayRef = 0; } } if (surface != 0) { @@ -518,11 +520,12 @@ status_t CameraService::Client::setOverlay() { CameraParameters params(mHardware->getParameters()); params.getPreviewSize(&w, &h); - if (w != mOverlayW || h != mOverlayH) { + if (w != mOverlayW || h != mOverlayH || mOrientationChanged) { // Force the destruction of any previous overlay sp<Overlay> dummy; mHardware->setOverlay(dummy); mOverlayRef = 0; + mOrientationChanged = false; } status_t result = NO_ERROR; @@ -802,6 +805,7 @@ status_t CameraService::Client::enableShutterSound(bool enable) { status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) { LOG1("sendCommand (pid %d)", getCallingPid()); + int orientation; Mutex::Autolock lock(mLock); status_t result = checkPidAndHardware(); if (result != NO_ERROR) return result; @@ -813,20 +817,24 @@ status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t a } switch (arg1) { case 0: - mOrientation = ISurface::BufferHeap::ROT_0; + orientation = ISurface::BufferHeap::ROT_0; break; case 90: - mOrientation = ISurface::BufferHeap::ROT_90; + orientation = ISurface::BufferHeap::ROT_90; break; case 180: - mOrientation = ISurface::BufferHeap::ROT_180; + orientation = ISurface::BufferHeap::ROT_180; break; case 270: - mOrientation = ISurface::BufferHeap::ROT_270; + orientation = ISurface::BufferHeap::ROT_270; break; default: return BAD_VALUE; } + if (mOrientation != orientation) { + mOrientation = orientation; + if (mOverlayRef != 0) mOrientationChanged = true; + } return OK; } else if (cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND) { switch (arg1) { diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h index 2b5c511..d57364a 100644 --- a/services/camera/libcameraservice/CameraService.h +++ b/services/camera/libcameraservice/CameraService.h @@ -173,7 +173,9 @@ private: int mOverlayW; int mOverlayH; int mPreviewCallbackFlag; - int mOrientation; + int mOrientation; // Current display orientation + // True if display orientation has been changed. This is only used in overlay. + int mOrientationChanged; bool mPlayShutterSound; // Ensures atomicity among the public methods diff --git a/services/java/com/android/server/EntropyService.java b/services/java/com/android/server/EntropyService.java index 81ae26f..0f1fc78 100644 --- a/services/java/com/android/server/EntropyService.java +++ b/services/java/com/android/server/EntropyService.java @@ -139,6 +139,7 @@ public class EntropyService extends Binder { out.println(SystemProperties.get("ro.bootloader")); out.println(SystemProperties.get("ro.hardware")); out.println(SystemProperties.get("ro.revision")); + out.println(new Object().hashCode()); out.println(System.currentTimeMillis()); out.println(System.nanoTime()); } catch (IOException e) { diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index 29ca9a4..fe306b3 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -77,6 +77,8 @@ public class InputManager { private static native InputDevice nativeGetInputDevice(int deviceId); private static native void nativeGetInputConfiguration(Configuration configuration); private static native int[] nativeGetInputDeviceIds(); + private static native boolean nativeTransferTouchFocus(InputChannel fromChannel, + InputChannel toChannel); private static native String nativeDump(); // Input event injection constants defined in InputDispatcher.h. @@ -320,6 +322,29 @@ public class InputManager { nativeSetInputDispatchMode(enabled, frozen); } + /** + * Atomically transfers touch focus from one window to another as identified by + * their input channels. It is possible for multiple windows to have + * touch focus if they support split touch dispatch + * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this + * method only transfers touch focus of the specified window without affecting + * other windows that may also have touch focus at the same time. + * @param fromChannel The channel of a window that currently has touch focus. + * @param toChannel The channel of the window that should receive touch focus in + * place of the first. + * @return True if the transfer was successful. False if the window with the + * specified channel did not actually have touch focus at the time of the request. + */ + public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { + if (fromChannel == null) { + throw new IllegalArgumentException("fromChannel must not be null."); + } + if (toChannel == null) { + throw new IllegalArgumentException("toChannel must not be null."); + } + return nativeTransferTouchFocus(fromChannel, toChannel); + } + public void dump(PrintWriter pw) { String dumpStr = nativeDump(); if (dumpStr != null) { diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 32f5e73..e6c6953 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -46,6 +46,7 @@ import android.os.storage.IObbActionListener; import android.os.storage.StorageResultCode; import android.util.Slog; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -150,7 +151,7 @@ class MountService extends IMountService.Stub * Mounted OBB tracking information. Used to track the current state of all * OBBs. */ - final private Map<IObbActionListener, ObbState> mObbMounts = new HashMap<IObbActionListener, ObbState>(); + final private Map<IObbActionListener, List<ObbState>> mObbMounts = new HashMap<IObbActionListener, List<ObbState>>(); final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>(); class ObbState implements IBinder.DeathRecipient { @@ -162,13 +163,13 @@ class MountService extends IMountService.Stub } // OBB source filename - String filename; + final String filename; // Token of remote Binder caller - IObbActionListener token; + final IObbActionListener token; // Binder.callingUid() - public int callerUid; + final public int callerUid; // Whether this is mounted currently. boolean mounted; @@ -227,9 +228,9 @@ class MountService extends IMountService.Stub private static final int MAX_UNMOUNT_RETRIES = 4; class UnmountCallBack { - String path; + final String path; + final boolean force; int retries; - boolean force; UnmountCallBack(String path, boolean force) { retries = 0; @@ -244,7 +245,7 @@ class MountService extends IMountService.Stub } class UmsEnableCallBack extends UnmountCallBack { - String method; + final String method; UmsEnableCallBack(String path, String method, boolean force) { super(path, force); @@ -1526,10 +1527,6 @@ class MountService extends IMountService.Stub throw new IllegalArgumentException("OBB file is already mounted"); } - if (mObbMounts.containsKey(token)) { - throw new IllegalArgumentException("You may only have one OBB mounted at a time"); - } - final int callerUid = Binder.getCallingUid(); obbState = new ObbState(filename, token, callerUid); addObbState(obbState); @@ -1567,14 +1564,25 @@ class MountService extends IMountService.Stub private void addObbState(ObbState obbState) { synchronized (mObbMounts) { - mObbMounts.put(obbState.token, obbState); + List<ObbState> obbStates = mObbMounts.get(obbState.token); + if (obbStates == null) { + obbStates = new ArrayList<ObbState>(); + mObbMounts.put(obbState.token, obbStates); + } + obbStates.add(obbState); mObbPathToStateMap.put(obbState.filename, obbState); } } private void removeObbState(ObbState obbState) { synchronized (mObbMounts) { - mObbMounts.remove(obbState.token); + final List<ObbState> obbStates = mObbMounts.get(obbState.token); + if (obbStates != null) { + obbStates.remove(obbState); + } + if (obbStates == null || obbStates.isEmpty()) { + mObbMounts.remove(obbState.token); + } mObbPathToStateMap.remove(obbState.filename); } } @@ -1750,7 +1758,7 @@ class MountService extends IMountService.Stub } } - abstract void handleExecute() throws RemoteException; + abstract void handleExecute() throws RemoteException, IOException; abstract void handleError(); } @@ -1762,8 +1770,12 @@ class MountService extends IMountService.Stub mKey = key; } - public void handleExecute() throws RemoteException { + public void handleExecute() throws RemoteException, IOException { ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename); + if (obbInfo == null) { + throw new IOException("Couldn't read OBB file"); + } + if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) { throw new IllegalArgumentException("Caller package does not match OBB file"); } @@ -1786,15 +1798,17 @@ class MountService extends IMountService.Stub if (rc == StorageResultCode.OperationSucceeded) { try { - mObbState.token.onObbResult(mObbState.filename, "mounted"); + mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_MOUNTED); } catch (RemoteException e) { Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); } } else { - Slog.e(TAG, "Couldn't mount OBB file"); + Slog.e(TAG, "Couldn't mount OBB file: " + rc); // We didn't succeed, so remove this from the mount-set. removeObbState(mObbState); + + mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL); } } @@ -1802,7 +1816,7 @@ class MountService extends IMountService.Stub removeObbState(mObbState); try { - mObbState.token.onObbResult(mObbState.filename, "error"); + mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL); } catch (RemoteException e) { Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename); } @@ -1831,8 +1845,11 @@ class MountService extends IMountService.Stub mForceUnmount = force; } - public void handleExecute() throws RemoteException { + public void handleExecute() throws RemoteException, IOException { ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename); + if (obbInfo == null) { + throw new IOException("Couldn't read OBB file"); + } if (!isCallerOwnerOfPackageOrSystem(obbInfo.packageName)) { throw new IllegalArgumentException("Caller package does not match OBB file"); @@ -1856,13 +1873,13 @@ class MountService extends IMountService.Stub removeObbState(mObbState); try { - mObbState.token.onObbResult(mObbState.filename, "unmounted"); + mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_UNMOUNTED); } catch (RemoteException e) { Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); } } else { try { - mObbState.token.onObbResult(mObbState.filename, "error"); + mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL); } catch (RemoteException e) { Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); } @@ -1873,7 +1890,7 @@ class MountService extends IMountService.Stub removeObbState(mObbState); try { - mObbState.token.onObbResult(mObbState.filename, "error"); + mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL); } catch (RemoteException e) { Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename); } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index c82a085..8f90756 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -91,6 +91,7 @@ import android.util.*; import android.view.Display; import android.view.WindowManager; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -8304,7 +8305,8 @@ class PackageManagerService extends IPackageManager.Stub { mPastSignatures.clear(); try { - FileOutputStream str = new FileOutputStream(mSettingsFilename); + BufferedOutputStream str = new BufferedOutputStream(new FileOutputStream( + mSettingsFilename)); //XmlSerializer serializer = XmlUtils.serializerInstance(); XmlSerializer serializer = new FastXmlSerializer(); @@ -8401,7 +8403,7 @@ class PackageManagerService extends IPackageManager.Stub { File tempFile = new File(mPackageListFilename.toString() + ".tmp"); JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile); - str = new FileOutputStream(journal.chooseForWrite()); + str = new BufferedOutputStream(new FileOutputStream(journal.chooseForWrite())); try { StringBuilder sb = new StringBuilder(); for (PackageSetting pkg : mPackages.values()) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 5ca386b..717f63c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -17,9 +17,9 @@ package com.android.server; import com.android.server.am.ActivityManagerService; -import com.android.server.sip.SipService; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; +import com.trustedlogic.trustednfc.android.server.NfcService; import dalvik.system.VMRuntime; import dalvik.system.Zygote; @@ -45,6 +45,7 @@ import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.server.search.SearchManagerService; import android.util.EventLog; +import android.util.Log; import android.util.Slog; import java.io.File; @@ -414,6 +415,20 @@ class ServerThread extends Thread { } catch (Throwable e) { Slog.e(TAG, "Failure starting Recognition Service", e); } + + try { + Slog.i(TAG, "Nfc Service"); + NfcService nfc; + try { + nfc = new NfcService(context); + } catch (UnsatisfiedLinkError e) { // gross hack to detect NFC + nfc = null; + Slog.w(TAG, "No NFC support"); + } + ServiceManager.addService(Context.NFC_SERVICE, nfc); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting NFC Service", e); + } try { Slog.i(TAG, "DiskStats Service"); @@ -435,16 +450,6 @@ class ServerThread extends Thread { } try { - SipService sipService = SipService.create(context); - if (sipService != null) { - Slog.i(TAG, "Sip Service"); - ServiceManager.addService("sip", sipService); - } - } catch (Throwable e) { - Slog.e(TAG, "Failure starting SIP Service", e); - } - - try { Slog.i(TAG, "NetworkTimeUpdateService"); networkTimeUpdater = new NetworkTimeUpdateService(context); } catch (Throwable e) { diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 7b2a570..19f56a8 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -21,7 +21,9 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -86,6 +88,7 @@ public class WifiService extends IWifiManager.Stub { private AlarmManager mAlarmManager; private PendingIntent mIdleIntent; + private BluetoothA2dp mBluetoothA2dp; private static final int IDLE_REQUEST = 0; private boolean mScreenOff; private boolean mDeviceIdle; @@ -182,7 +185,7 @@ public class WifiService extends IWifiManager.Stub { * something other than scanning, we reset this to 0. */ private int mNumScansSinceNetworkStateChange; - + /** * Temporary for computing UIDS that are responsible for starting WIFI. * Protected by mWifiStateTracker lock. @@ -888,17 +891,10 @@ public class WifiService extends IWifiManager.Stub { return; } mPluggedType = pluggedType; - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - Set<BluetoothDevice> sinks = a2dp.getConnectedSinks(); - boolean isBluetoothPlaying = false; - for (BluetoothDevice sink : sinks) { - if (a2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) { - isBluetoothPlaying = true; - } - } - mWifiStateMachine.setBluetoothScanMode(isBluetoothPlaying); - + } else if (action.equals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, + BluetoothA2dp.STATE_NOT_PLAYING); + mWifiStateMachine.setBluetoothScanMode(state == BluetoothA2dp.STATE_PLAYING); } else { return; } @@ -958,7 +954,7 @@ public class WifiService extends IWifiManager.Stub { } mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource); } - + private void updateWifiState() { boolean wifiEnabled = getPersistedWifiEnabled(); boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get(); @@ -999,7 +995,7 @@ public class WifiService extends IWifiManager.Stub { intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); intentFilter.addAction(ACTION_DEVICE_IDLE); - intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + intentFilter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); mContext.registerReceiver(mReceiver, intentFilter); } @@ -1191,7 +1187,7 @@ public class WifiService extends IWifiManager.Stub { } private boolean acquireWifiLockLocked(WifiLock wifiLock) { - Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock); + if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock); mLocks.addLock(wifiLock); @@ -1214,7 +1210,7 @@ public class WifiService extends IWifiManager.Stub { // Be aggressive about adding new locks into the accounted state... // we want to over-report rather than under-report. reportStartWorkSource(); - + updateWifiState(); return true; } @@ -1258,7 +1254,7 @@ public class WifiService extends IWifiManager.Stub { WifiLock wifiLock = mLocks.removeLock(lock); - Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock); + if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock); hadLock = (wifiLock != null); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 9685fb7..5c4b919 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -4225,29 +4225,75 @@ public final class ActivityManagerService extends ActivityManagerNative } private final boolean checkHoldingPermissionsLocked(IPackageManager pm, - ProviderInfo pi, int uid, int modeFlags) { + ProviderInfo pi, Uri uri, int uid, int modeFlags) { + boolean readPerm = (modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0; + boolean writePerm = (modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0; + if (DEBUG_URI_PERMISSION) Slog.v(TAG, + "checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid); try { - if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { - if ((pi.readPermission != null) && + // Is the component private from the target uid? + final boolean prv = !pi.exported && pi.applicationInfo.uid != uid; + + // Acceptable if the there is no read permission needed from the + // target or the target is holding the read permission. + if (!readPerm) { + if ((!prv && pi.readPermission == null) || (pm.checkUidPermission(pi.readPermission, uid) - != PackageManager.PERMISSION_GRANTED)) { - return false; + == PackageManager.PERMISSION_GRANTED)) { + readPerm = true; } } - if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { - if ((pi.writePermission != null) && + + // Acceptable if the there is no write permission needed from the + // target or the target is holding the read permission. + if (!writePerm) { + if (!prv && (pi.writePermission == null) || (pm.checkUidPermission(pi.writePermission, uid) - != PackageManager.PERMISSION_GRANTED)) { - return false; + == PackageManager.PERMISSION_GRANTED)) { + writePerm = true; } } - if (!pi.exported && pi.applicationInfo.uid != uid) { - return false; + + // Acceptable if there is a path permission matching the URI that + // the target holds the permission on. + PathPermission[] pps = pi.pathPermissions; + if (pps != null && (!readPerm || !writePerm)) { + final String path = uri.getPath(); + int i = pps.length; + while (i > 0 && (!readPerm || !writePerm)) { + i--; + PathPermission pp = pps[i]; + if (!readPerm) { + final String pprperm = pp.getReadPermission(); + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for " + + pprperm + " for " + pp.getPath() + + ": match=" + pp.match(path) + + " check=" + pm.checkUidPermission(pprperm, uid)); + if (pprperm != null && pp.match(path) && + (pm.checkUidPermission(pprperm, uid) + == PackageManager.PERMISSION_GRANTED)) { + readPerm = true; + } + } + if (!writePerm) { + final String ppwperm = pp.getWritePermission(); + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm " + + ppwperm + " for " + pp.getPath() + + ": match=" + pp.match(path) + + " check=" + pm.checkUidPermission(ppwperm, uid)); + if (ppwperm != null && pp.match(path) && + (pm.checkUidPermission(ppwperm, uid) + == PackageManager.PERMISSION_GRANTED)) { + writePerm = true; + } + } + } } - return true; } catch (RemoteException e) { return false; } + + return readPerm && writePerm; } private final boolean checkUriPermissionLocked(Uri uri, int uid, @@ -4340,7 +4386,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // First... does the target actually need this permission? - if (checkHoldingPermissionsLocked(pm, pi, targetUid, modeFlags)) { + if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) { // No need to grant the target this permission. if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Target " + targetPkg + " already has full permission to " + uri); @@ -4374,7 +4420,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Third... does the caller itself have permission to access // this uri? - if (!checkHoldingPermissionsLocked(pm, pi, callingUid, modeFlags)) { + if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { throw new SecurityException("Uid " + callingUid + " does not have permission to uri " + uri); @@ -4542,7 +4588,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // Does the caller have this permission on the URI? - if (!checkHoldingPermissionsLocked(pm, pi, callingUid, modeFlags)) { + if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { // Right now, if you are not the original owner of the permission, // you are not allowed to revoke it. //if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { @@ -5599,6 +5645,38 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * Allows app to retrieve the MIME type of a URI without having permission + * to access its content provider. + * + * CTS tests for this functionality can be run with "runtest cts-appsecurity". + * + * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ + * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java + */ + public String getProviderMimeType(Uri uri) { + final String name = uri.getAuthority(); + final long ident = Binder.clearCallingIdentity(); + ContentProviderHolder holder = null; + + try { + holder = getContentProviderExternal(name); + if (holder != null) { + return holder.provider.getType(uri); + } + } catch (RemoteException e) { + Log.w(TAG, "Content provider dead retrieving " + uri, e); + return null; + } finally { + if (holder != null) { + removeContentProviderExternal(name); + } + Binder.restoreCallingIdentity(ident); + } + + return null; + } + // ========================================================= // GLOBAL MANAGEMENT // ========================================================= diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 73a5435..0a98ebd 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -16,7 +16,9 @@ package com.android.server.am; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -43,6 +45,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub { final BatteryStatsImpl mStats; Context mContext; + private boolean mBluetoothPendingStats; + private BluetoothHeadset mBluetoothHeadset; BatteryStatsService(String filename) { mStats = new BatteryStatsImpl(filename); @@ -283,16 +287,43 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void noteBluetoothOn() { enforceCallingPermission(); - BluetoothHeadset headset = new BluetoothHeadset(mContext, null); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEADSET); + } synchronized (mStats) { - mStats.noteBluetoothOnLocked(); - mStats.setBtHeadset(headset); + if (mBluetoothHeadset != null) { + mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(mBluetoothHeadset); + } else { + mBluetoothPendingStats = true; + } } } - + + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothHeadset = (BluetoothHeadset) proxy; + synchronized (mStats) { + if (mBluetoothPendingStats) { + mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(mBluetoothHeadset); + mBluetoothPendingStats = false; + } + } + } + + public void onServiceDisconnected(int profile) { + mBluetoothHeadset = null; + } + }; + public void noteBluetoothOff() { enforceCallingPermission(); synchronized (mStats) { + mBluetoothPendingStats = false; mStats.noteBluetoothOffLocked(); } } diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java index 0cb6943..e3347cb 100644 --- a/services/java/com/android/server/am/UriPermission.java +++ b/services/java/com/android/server/am/UriPermission.java @@ -27,8 +27,8 @@ import java.util.HashSet; * * CTS tests for this functionality can be run with "runtest cts-appsecurity". * - * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert - * /src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java + * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ + * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java */ class UriPermission { final int uid; diff --git a/services/java/com/trustedlogic/trustednfc/android/server/NfcService.java b/services/java/com/trustedlogic/trustednfc/android/server/NfcService.java new file mode 100644 index 0000000..431b798 --- /dev/null +++ b/services/java/com/trustedlogic/trustednfc/android/server/NfcService.java @@ -0,0 +1,2111 @@ +/* + * 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.trustedlogic.trustednfc.android.server; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.Set; + +import com.trustedlogic.trustednfc.android.ILlcpConnectionlessSocket; +import com.trustedlogic.trustednfc.android.ILlcpServiceSocket; +import com.trustedlogic.trustednfc.android.INfcManager; +import com.trustedlogic.trustednfc.android.ILlcpSocket; +import com.trustedlogic.trustednfc.android.INfcTag; +import com.trustedlogic.trustednfc.android.IP2pInitiator; +import com.trustedlogic.trustednfc.android.IP2pTarget; +import com.trustedlogic.trustednfc.android.LlcpPacket; +import com.trustedlogic.trustednfc.android.NdefMessage; +import com.trustedlogic.trustednfc.android.NfcException; +import com.trustedlogic.trustednfc.android.NfcManager; +import com.trustedlogic.trustednfc.android.internal.NativeLlcpConnectionlessSocket; +import com.trustedlogic.trustednfc.android.internal.NativeLlcpServiceSocket; +import com.trustedlogic.trustednfc.android.internal.NativeLlcpSocket; +import com.trustedlogic.trustednfc.android.internal.NativeNfcManager; +import com.trustedlogic.trustednfc.android.internal.NativeNfcTag; +import com.trustedlogic.trustednfc.android.internal.NativeP2pDevice; +import com.trustedlogic.trustednfc.android.internal.ErrorCodes; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +public class NfcService extends INfcManager.Stub implements Runnable { + + /** + * NFC Service tag + */ + private static final String TAG = "NfcService"; + + /** + * NFC features disabled state + */ + private static final short NFC_STATE_DISABLED = 0x00; + + /** + * NFC features enabled state + */ + private static final short NFC_STATE_ENABLED = 0x01; + + /** + * NFC Discovery for Reader mode + */ + private static final int DISCOVERY_MODE_READER = 0; + + /** + * NFC Discovery for Card Emulation Mode + */ + private static final int DISCOVERY_MODE_CARD_EMULATION = 2; + + /** + * LLCP Service Socket type + */ + private static final int LLCP_SERVICE_SOCKET_TYPE = 0; + + /** + * LLCP Socket type + */ + private static final int LLCP_SOCKET_TYPE = 1; + + /** + * LLCP Connectionless socket type + */ + private static final int LLCP_CONNECTIONLESS_SOCKET_TYPE = 2; + + /** + * Maximun number of sockets managed + */ + private static final int LLCP_SOCKET_NB_MAX = 5; + + /** + * Default value for the Maximum Information Unit parameter + */ + private static final int LLCP_LTO_DEFAULT_VALUE = 150; + + /** + * Default value for the Maximum Information Unit parameter + */ + private static final int LLCP_LTO_MAX_VALUE = 255; + + /** + * Maximun value for the Receive Window + */ + private static final int LLCP_RW_MAX_VALUE = 15; + + /** + * Default value for the Maximum Information Unit parameter + */ + private static final int LLCP_MIU_DEFAULT_VALUE = 128; + + /** + * Default value for the Maximum Information Unit parameter + */ + private static final int LLCP_MIU_MAX_VALUE = 2176; + + /** + * Default value for the Well Known Service List parameter + */ + private static final int LLCP_WKS_DEFAULT_VALUE = 1; + + /** + * Max value for the Well Known Service List parameter + */ + private static final int LLCP_WKS_MAX_VALUE = 15; + + /** + * Default value for the Option parameter + */ + private static final int LLCP_OPT_DEFAULT_VALUE = 0; + + /** + * Max value for the Option parameter + */ + private static final int LLCP_OPT_MAX_VALUE = 3; + + /** + * LLCP Properties + */ + private static final int PROPERTY_LLCP_LTO = 0; + + private static final int PROPERTY_LLCP_MIU = 1; + + private static final int PROPERTY_LLCP_WKS = 2; + + private static final int PROPERTY_LLCP_OPT = 3; + + private static final String PROPERTY_LLCP_LTO_VALUE = "llcp.lto"; + + private static final String PROPERTY_LLCP_MIU_VALUE = "llcp.miu"; + + private static final String PROPERTY_LLCP_WKS_VALUE = "llcp.wks"; + + private static final String PROPERTY_LLCP_OPT_VALUE = "llcp.opt"; + + /** + * NFC Reader Properties + */ + private static final int PROPERTY_NFC_DISCOVERY_A = 4; + + private static final int PROPERTY_NFC_DISCOVERY_B = 5; + + private static final int PROPERTY_NFC_DISCOVERY_F = 6; + + private static final int PROPERTY_NFC_DISCOVERY_15693 = 7; + + private static final int PROPERTY_NFC_DISCOVERY_NFCIP = 8; + + private static final String PROPERTY_NFC_DISCOVERY_A_VALUE = "discovery.iso14443A"; + + private static final String PROPERTY_NFC_DISCOVERY_B_VALUE = "discovery.iso14443B"; + + private static final String PROPERTY_NFC_DISCOVERY_F_VALUE = "discovery.felica"; + + private static final String PROPERTY_NFC_DISCOVERY_15693_VALUE = "discovery.iso15693"; + + private static final String PROPERTY_NFC_DISCOVERY_NFCIP_VALUE = "discovery.nfcip"; + + private Context mContext; + + private HashMap<Integer, Object> mObjectMap = new HashMap<Integer, Object>(); + + private HashMap<Integer, Object> mSocketMap = new HashMap<Integer, Object>(); + + private LinkedList<RegisteredSocket> mRegisteredSocketList = new LinkedList<RegisteredSocket>(); + + private int mLlcpLinkState = NfcManager.LLCP_LINK_STATE_DEACTIVATED; + + private int mGeneratedSocketHandle = 0; + + private int mNbSocketCreated = 0; + + private boolean mIsNfcEnabled = false; + + private NfcHandler mNfcHandler; + + private int mSelectedSeId = 0; + + private int mTimeout = 0; + + private int mNfcState; + + private int mNfcSecureElementState; + + private boolean mOpenPending = false; + + private NativeNfcManager mManager; + + private ILlcpSocket mLlcpSocket = new ILlcpSocket.Stub() { + + public int close(int nativeHandle) throws RemoteException { + NativeLlcpSocket socket = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_ACTIVATED) { + isSuccess = socket.doClose(); + if (isSuccess) { + /* Remove the socket closed from the hmap */ + RemoveSocket(nativeHandle); + /* Update mNbSocketCreated */ + mNbSocketCreated--; + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + } else { + /* Remove the socket closed from the hmap */ + RemoveSocket(nativeHandle); + + /* Remove registered socket from the list */ + RemoveRegisteredSocket(nativeHandle); + + /* Update mNbSocketCreated */ + mNbSocketCreated--; + + return ErrorCodes.SUCCESS; + } + } else { + return ErrorCodes.ERROR_IO; + } + } + + public int connect(int nativeHandle, int sap) throws RemoteException { + NativeLlcpSocket socket = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + isSuccess = socket.doConnect(sap, socket.getConnectTimeout()); + if (isSuccess) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_IO; + } + + } + + public int connectByName(int nativeHandle, String sn) throws RemoteException { + NativeLlcpSocket socket = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + isSuccess = socket.doConnectBy(sn, socket.getConnectTimeout()); + if (isSuccess) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_IO; + } + + } + + public int getConnectTimeout(int nativeHandle) throws RemoteException { + NativeLlcpSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + return socket.getConnectTimeout(); + } else { + return 0; + } + } + + public int getLocalSap(int nativeHandle) throws RemoteException { + NativeLlcpSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + return socket.getSap(); + } else { + return 0; + } + } + + public int getLocalSocketMiu(int nativeHandle) throws RemoteException { + NativeLlcpSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + return socket.getMiu(); + } else { + return 0; + } + } + + public int getLocalSocketRw(int nativeHandle) throws RemoteException { + NativeLlcpSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + return socket.getRw(); + } else { + return 0; + } + } + + public int getRemoteSocketMiu(int nativeHandle) throws RemoteException { + NativeLlcpSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + if (socket.doGetRemoteSocketMiu() != 0) { + return socket.doGetRemoteSocketMiu(); + } else { + return ErrorCodes.ERROR_SOCKET_NOT_CONNECTED; + } + } else { + return ErrorCodes.ERROR_SOCKET_NOT_CONNECTED; + } + } + + public int getRemoteSocketRw(int nativeHandle) throws RemoteException { + NativeLlcpSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + if (socket.doGetRemoteSocketRw() != 0) { + return socket.doGetRemoteSocketRw(); + } else { + return ErrorCodes.ERROR_SOCKET_NOT_CONNECTED; + } + } else { + return ErrorCodes.ERROR_SOCKET_NOT_CONNECTED; + } + } + + public int receive(int nativeHandle, byte[] receiveBuffer) throws RemoteException { + NativeLlcpSocket socket = null; + int receiveLength = 0; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + receiveLength = socket.doReceive(receiveBuffer); + if (receiveLength != 0) { + return receiveLength; + } else { + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_IO; + } + } + + public int send(int nativeHandle, byte[] data) throws RemoteException { + NativeLlcpSocket socket = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + isSuccess = socket.doSend(data); + if (isSuccess) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_IO; + } + } + + public void setConnectTimeout(int nativeHandle, int timeout) throws RemoteException { + NativeLlcpSocket socket = null; + + /* find the socket in the hmap */ + socket = (NativeLlcpSocket) findSocket(nativeHandle); + if (socket != null) { + socket.setConnectTimeout(timeout); + } + } + + }; + + private ILlcpServiceSocket mLlcpServerSocketService = new ILlcpServiceSocket.Stub() { + + public int accept(int nativeHandle) throws RemoteException { + NativeLlcpServiceSocket socket = null; + NativeLlcpSocket clientSocket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + if (mNbSocketCreated < LLCP_SOCKET_NB_MAX) { + /* find the socket in the hmap */ + socket = (NativeLlcpServiceSocket) findSocket(nativeHandle); + if (socket != null) { + clientSocket = socket.doAccept(socket.getAcceptTimeout(), socket.getMiu(), + socket.getRw(), socket.getLinearBufferLength()); + if (clientSocket != null) { + /* Add the socket into the socket map */ + mSocketMap.put(clientSocket.getHandle(), clientSocket); + mNbSocketCreated++; + return clientSocket.getHandle(); + } else { + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_INSUFFICIENT_RESOURCES; + } + + } + + public void close(int nativeHandle) throws RemoteException { + NativeLlcpServiceSocket socket = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpServiceSocket) findSocket(nativeHandle); + if (socket != null) { + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_ACTIVATED) { + isSuccess = socket.doClose(); + if (isSuccess) { + /* Remove the socket closed from the hmap */ + RemoveSocket(nativeHandle); + /* Update mNbSocketCreated */ + mNbSocketCreated--; + } + } else { + /* Remove the socket closed from the hmap */ + RemoveSocket(nativeHandle); + + /* Remove registered socket from the list */ + RemoveRegisteredSocket(nativeHandle); + + /* Update mNbSocketCreated */ + mNbSocketCreated--; + } + } + } + + public int getAcceptTimeout(int nativeHandle) throws RemoteException { + NativeLlcpServiceSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpServiceSocket) findSocket(nativeHandle); + if (socket != null) { + return socket.getAcceptTimeout(); + } else { + return 0; + } + } + + public void setAcceptTimeout(int nativeHandle, int timeout) throws RemoteException { + NativeLlcpServiceSocket socket = null; + + /* find the socket in the hmap */ + socket = (NativeLlcpServiceSocket) findSocket(nativeHandle); + if (socket != null) { + socket.setAcceptTimeout(timeout); + } + } + }; + + private ILlcpConnectionlessSocket mLlcpConnectionlessSocketService = new ILlcpConnectionlessSocket.Stub() { + + public void close(int nativeHandle) throws RemoteException { + NativeLlcpConnectionlessSocket socket = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpConnectionlessSocket) findSocket(nativeHandle); + if (socket != null) { + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_ACTIVATED) { + isSuccess = socket.doClose(); + if (isSuccess) { + /* Remove the socket closed from the hmap */ + RemoveSocket(nativeHandle); + /* Update mNbSocketCreated */ + mNbSocketCreated--; + } + } else { + /* Remove the socket closed from the hmap */ + RemoveSocket(nativeHandle); + + /* Remove registered socket from the list */ + RemoveRegisteredSocket(nativeHandle); + + /* Update mNbSocketCreated */ + mNbSocketCreated--; + } + } + } + + public int getSap(int nativeHandle) throws RemoteException { + NativeLlcpConnectionlessSocket socket = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpConnectionlessSocket) findSocket(nativeHandle); + if (socket != null) { + return socket.getSap(); + } else { + return 0; + } + } + + public LlcpPacket receiveFrom(int nativeHandle) throws RemoteException { + NativeLlcpConnectionlessSocket socket = null; + LlcpPacket packet; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpConnectionlessSocket) findSocket(nativeHandle); + if (socket != null) { + packet = socket.doReceiveFrom(socket.getLinkMiu()); + if (packet != null) { + return packet; + } + return null; + } else { + return null; + } + } + + public int sendTo(int nativeHandle, LlcpPacket packet) throws RemoteException { + NativeLlcpConnectionlessSocket socket = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the socket in the hmap */ + socket = (NativeLlcpConnectionlessSocket) findSocket(nativeHandle); + if (socket != null) { + isSuccess = socket.doSendTo(packet.getRemoteSap(), packet.getDataBuffer()); + if (isSuccess) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_IO; + } + } + }; + + private INfcTag mNfcTagService = new INfcTag.Stub() { + + public int close(int nativeHandle) throws RemoteException { + NativeNfcTag tag = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + if (tag.doDisconnect()) { + /* Remove the device from the hmap */ + RemoveObject(nativeHandle); + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + mOpenPending = false; + return ErrorCodes.SUCCESS; + } + + } + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + mOpenPending = false; + return ErrorCodes.ERROR_DISCONNECT; + } + + public int connect(int nativeHandle) throws RemoteException { + NativeNfcTag tag = null; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + if (tag.doConnect()) + return ErrorCodes.SUCCESS; + } + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + mOpenPending = false; + return ErrorCodes.ERROR_CONNECT; + } + + public String getType(int nativeHandle) throws RemoteException { + NativeNfcTag tag = null; + String type; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + type = tag.getType(); + return type; + } + return null; + } + + public byte[] getUid(int nativeHandle) throws RemoteException { + NativeNfcTag tag = null; + byte[] uid; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + uid = tag.getUid(); + return uid; + } + return null; + } + + public boolean isNdef(int nativeHandle) throws RemoteException { + NativeNfcTag tag = null; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return isSuccess; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + isSuccess = tag.checkNDEF(); + } + return isSuccess; + } + + public byte[] transceive(int nativeHandle, byte[] data) throws RemoteException { + NativeNfcTag tag = null; + byte[] response; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + response = tag.doTransceive(data); + return response; + } + return null; + } + + public NdefMessage read(int nativeHandle) throws RemoteException { + NativeNfcTag tag; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + byte[] buf = tag.doRead(); + if (buf == null) + return null; + + /* Create an NdefMessage */ + try { + return new NdefMessage(buf); + } catch (NfcException e) { + return null; + } + } + return null; + } + + public boolean write(int nativeHandle, NdefMessage msg) throws RemoteException { + NativeNfcTag tag; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return isSuccess; + } + + /* find the tag in the hmap */ + tag = (NativeNfcTag) findObject(nativeHandle); + if (tag != null) { + isSuccess = tag.doWrite(msg.toByteArray()); + } + return isSuccess; + + } + + }; + + private IP2pInitiator mP2pInitiatorService = new IP2pInitiator.Stub() { + + public byte[] getGeneralBytes(int nativeHandle) throws RemoteException { + NativeP2pDevice device; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + byte[] buff = device.getGeneralBytes(); + if (buff == null) + return null; + return buff; + } + return null; + } + + public int getMode(int nativeHandle) throws RemoteException { + NativeP2pDevice device; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + return device.getMode(); + } + return ErrorCodes.ERROR_INVALID_PARAM; + } + + public byte[] receive(int nativeHandle) throws RemoteException { + NativeP2pDevice device; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + byte[] buff = device.doReceive(); + if (buff == null) + return null; + return buff; + } + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + mOpenPending = false; + return null; + } + + public boolean send(int nativeHandle, byte[] data) throws RemoteException { + NativeP2pDevice device; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return isSuccess; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + isSuccess = device.doSend(data); + } + return isSuccess; + } + }; + + private IP2pTarget mP2pTargetService = new IP2pTarget.Stub() { + + public int connect(int nativeHandle) throws RemoteException { + NativeP2pDevice device; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + if (device.doConnect()) { + return ErrorCodes.SUCCESS; + } + } + return ErrorCodes.ERROR_CONNECT; + } + + public boolean disconnect(int nativeHandle) throws RemoteException { + NativeP2pDevice device; + boolean isSuccess = false; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return isSuccess; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + if (isSuccess = device.doDisconnect()) { + mOpenPending = false; + /* remove the device from the hmap */ + RemoveObject(nativeHandle); + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + } + } + return isSuccess; + + } + + public byte[] getGeneralBytes(int nativeHandle) throws RemoteException { + NativeP2pDevice device; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + byte[] buff = device.getGeneralBytes(); + if (buff == null) + return null; + return buff; + } + return null; + } + + public int getMode(int nativeHandle) throws RemoteException { + NativeP2pDevice device; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + return device.getMode(); + } + return ErrorCodes.ERROR_INVALID_PARAM; + } + + public byte[] transceive(int nativeHandle, byte[] data) throws RemoteException { + NativeP2pDevice device; + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return null; + } + + /* find the device in the hmap */ + device = (NativeP2pDevice) findObject(nativeHandle); + if (device != null) { + byte[] buff = device.doTransceive(data); + if (buff == null) + return null; + return buff; + } + return null; + } + }; + + private class NfcHandler extends Handler { + + @Override + public void handleMessage(Message msg) { + try { + + } catch (Exception e) { + // Log, don't crash! + Log.e(TAG, "Exception in NfcHandler.handleMessage:", e); + } + } + + }; + + public NfcService(Context context) { + super(); + mContext = context; + mManager = new NativeNfcManager(mContext); + + mContext.registerReceiver(mNfcServiceReceiver, new IntentFilter( + NativeNfcManager.INTERNAL_LLCP_LINK_STATE_CHANGED_ACTION)); + + mContext.registerReceiver(mNfcServiceReceiver, new IntentFilter( + NfcManager.LLCP_LINK_STATE_CHANGED_ACTION)); + + mContext.registerReceiver(mNfcServiceReceiver, new IntentFilter( + NativeNfcManager.INTERNAL_TARGET_DESELECTED_ACTION)); + + Thread thread = new Thread(null, this, "NfcService"); + thread.start(); + + mManager.initializeNativeStructure(); + + int nfcState = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_ON, 0); + + if (nfcState == NFC_STATE_ENABLED) { + if (this._enable()) { + } + } + + } + + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + Looper.prepare(); + mNfcHandler = new NfcHandler(); + Looper.loop(); + } + + public void cancel() throws RemoteException { + mContext.enforceCallingPermission(android.Manifest.permission.NFC_RAW, + "NFC_RAW permission required to cancel NFC opening"); + if (mOpenPending) { + mOpenPending = false; + mManager.doCancel(); + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + } + } + + public int createLlcpConnectionlessSocket(int sap) throws RemoteException { + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + mContext.enforceCallingPermission(android.Manifest.permission.NFC_LLCP, + "NFC_LLCP permission required for LLCP operations with NFC service"); + + /* Check SAP is not already used */ + + /* Check nb socket created */ + if (mNbSocketCreated < LLCP_SOCKET_NB_MAX) { + /* Store the socket handle */ + int sockeHandle = mGeneratedSocketHandle; + + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_ACTIVATED) { + NativeLlcpConnectionlessSocket socket; + + socket = mManager.doCreateLlcpConnectionlessSocket(sap); + if (socket != null) { + /* Update the number of socket created */ + mNbSocketCreated++; + + /* Add the socket into the socket map */ + mSocketMap.put(sockeHandle, socket); + + return sockeHandle; + } else { + /* + * socket creation error - update the socket handle + * generation + */ + mGeneratedSocketHandle -= 1; + + /* Get Error Status */ + int errorStatus = mManager.doGetLastError(); + + switch (errorStatus) { + case ErrorCodes.ERROR_BUFFER_TO_SMALL: + return ErrorCodes.ERROR_BUFFER_TO_SMALL; + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + return ErrorCodes.ERROR_INSUFFICIENT_RESOURCES; + default: + return ErrorCodes.ERROR_SOCKET_CREATION; + } + } + } else { + /* Check SAP is not already used */ + if (!CheckSocketSap(sap)) { + return ErrorCodes.ERROR_SAP_USED; + } + + NativeLlcpConnectionlessSocket socket = new NativeLlcpConnectionlessSocket(sap); + + /* Add the socket into the socket map */ + mSocketMap.put(sockeHandle, socket); + + /* Update the number of socket created */ + mNbSocketCreated++; + + /* Create new registered socket */ + RegisteredSocket registeredSocket = new RegisteredSocket( + LLCP_CONNECTIONLESS_SOCKET_TYPE, sockeHandle, sap); + + /* Put this socket into a list of registered socket */ + mRegisteredSocketList.add(registeredSocket); + } + + /* update socket handle generation */ + mGeneratedSocketHandle++; + + return sockeHandle; + + } else { + /* No socket available */ + return ErrorCodes.ERROR_INSUFFICIENT_RESOURCES; + } + + } + + public int createLlcpServiceSocket(int sap, String sn, int miu, int rw, int linearBufferLength) + throws RemoteException { + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + mContext.enforceCallingPermission(android.Manifest.permission.NFC_LLCP, + "NFC_LLCP permission required for LLCP operations with NFC service"); + + if (mNbSocketCreated < LLCP_SOCKET_NB_MAX) { + int sockeHandle = mGeneratedSocketHandle; + + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_ACTIVATED) { + NativeLlcpServiceSocket socket; + + socket = mManager.doCreateLlcpServiceSocket(sap, sn, miu, rw, linearBufferLength); + if (socket != null) { + /* Update the number of socket created */ + mNbSocketCreated++; + /* Add the socket into the socket map */ + mSocketMap.put(sockeHandle, socket); + } else { + /* socket creation error - update the socket handle counter */ + mGeneratedSocketHandle -= 1; + + /* Get Error Status */ + int errorStatus = mManager.doGetLastError(); + + switch (errorStatus) { + case ErrorCodes.ERROR_BUFFER_TO_SMALL: + return ErrorCodes.ERROR_BUFFER_TO_SMALL; + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + return ErrorCodes.ERROR_INSUFFICIENT_RESOURCES; + default: + return ErrorCodes.ERROR_SOCKET_CREATION; + } + } + } else { + + /* Check SAP is not already used */ + if (!CheckSocketSap(sap)) { + return ErrorCodes.ERROR_SAP_USED; + } + + /* Service Name */ + if (!CheckSocketServiceName(sn)) { + return ErrorCodes.ERROR_SERVICE_NAME_USED; + } + + /* Check socket options */ + if (!CheckSocketOptions(miu, rw, linearBufferLength)) { + return ErrorCodes.ERROR_SOCKET_OPTIONS; + } + + NativeLlcpServiceSocket socket = new NativeLlcpServiceSocket(sn); + + /* Add the socket into the socket map */ + mSocketMap.put(sockeHandle, socket); + + /* Update the number of socket created */ + mNbSocketCreated++; + + /* Create new registered socket */ + RegisteredSocket registeredSocket = new RegisteredSocket(LLCP_SERVICE_SOCKET_TYPE, + sockeHandle, sap, sn, miu, rw, linearBufferLength); + + /* Put this socket into a list of registered socket */ + mRegisteredSocketList.add(registeredSocket); + } + + /* update socket handle generation */ + mGeneratedSocketHandle += 1; + + Log.d(TAG, "Llcp Service Socket Handle =" + sockeHandle); + return sockeHandle; + } else { + /* No socket available */ + return ErrorCodes.ERROR_INSUFFICIENT_RESOURCES; + } + } + + public int createLlcpSocket(int sap, int miu, int rw, int linearBufferLength) + throws RemoteException { + + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + mContext.enforceCallingPermission(android.Manifest.permission.NFC_LLCP, + "NFC_LLCP permission required for LLCP operations with NFC service"); + + if (mNbSocketCreated < LLCP_SOCKET_NB_MAX) { + + int sockeHandle = mGeneratedSocketHandle; + + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_ACTIVATED) { + NativeLlcpSocket socket; + + socket = mManager.doCreateLlcpSocket(sap, miu, rw, linearBufferLength); + + if (socket != null) { + /* Update the number of socket created */ + mNbSocketCreated++; + /* Add the socket into the socket map */ + mSocketMap.put(sockeHandle, socket); + } else { + /* + * socket creation error - update the socket handle + * generation + */ + mGeneratedSocketHandle -= 1; + + /* Get Error Status */ + int errorStatus = mManager.doGetLastError(); + + switch (errorStatus) { + case ErrorCodes.ERROR_BUFFER_TO_SMALL: + return ErrorCodes.ERROR_BUFFER_TO_SMALL; + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + return ErrorCodes.ERROR_INSUFFICIENT_RESOURCES; + default: + return ErrorCodes.ERROR_SOCKET_CREATION; + } + } + } else { + + /* Check SAP is not already used */ + if (!CheckSocketSap(sap)) { + return ErrorCodes.ERROR_SAP_USED; + } + + /* Check Socket options */ + if (!CheckSocketOptions(miu, rw, linearBufferLength)) { + return ErrorCodes.ERROR_SOCKET_OPTIONS; + } + + NativeLlcpSocket socket = new NativeLlcpSocket(sap, miu, rw); + + /* Add the socket into the socket map */ + mSocketMap.put(sockeHandle, socket); + + /* Update the number of socket created */ + mNbSocketCreated++; + /* Create new registered socket */ + RegisteredSocket registeredSocket = new RegisteredSocket(LLCP_SOCKET_TYPE, + sockeHandle, sap, miu, rw, linearBufferLength); + + /* Put this socket into a list of registered socket */ + mRegisteredSocketList.add(registeredSocket); + } + + /* update socket handle generation */ + mGeneratedSocketHandle++; + + return sockeHandle; + } else { + /* No socket available */ + return ErrorCodes.ERROR_INSUFFICIENT_RESOURCES; + } + } + + public int deselectSecureElement() throws RemoteException { + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + if (mSelectedSeId == 0) { + return ErrorCodes.ERROR_NO_SE_CONNECTED; + } + + mContext.enforceCallingPermission(android.Manifest.permission.NFC_ADMIN, + "NFC_ADMIN permission required to deselect NFC Secure Element"); + + mManager.doDeselectSecureElement(mSelectedSeId); + mNfcSecureElementState = 0; + mSelectedSeId = 0; + + /* Store that a secure element is deselected */ + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NFC_SECURE_ELEMENT_ON, 0); + + /* Reset Secure Element ID */ + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NFC_SECURE_ELEMENT_ID, 0); + + + return ErrorCodes.SUCCESS; + } + + public boolean disable() throws RemoteException { + boolean isSuccess = false; + mContext.enforceCallingPermission(android.Manifest.permission.NFC_ADMIN, + "NFC_ADMIN permission required to disable NFC service"); + if (isEnabled()) { + isSuccess = mManager.deinitialize(); + if (isSuccess) { + mIsNfcEnabled = false; + } + } + + updateNfcOnSetting(); + + return isSuccess; + } + + public boolean enable() throws RemoteException { + boolean isSuccess = false; + mContext.enforceCallingPermission(android.Manifest.permission.NFC_ADMIN, + "NFC_ADMIN permission required to enable NFC service"); + if (!isEnabled()) { + reset(); + isSuccess = _enable(); + } + return isSuccess; + } + + private boolean _enable() { + boolean isSuccess = mManager.initialize(); + if (isSuccess) { + /* Check persistent properties */ + checkProperties(); + + /* Check Secure Element setting */ + mNfcSecureElementState = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_SECURE_ELEMENT_ON, 0); + + if (mNfcSecureElementState == 1) { + + int secureElementId = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_SECURE_ELEMENT_ID, 0); + int[] Se_list = mManager.doGetSecureElementList(); + if (Se_list != null) { + for (int i = 0; i < Se_list.length; i++) { + if (Se_list[i] == secureElementId) { + mManager.doSelectSecureElement(Se_list[i]); + mSelectedSeId = Se_list[i]; + break; + } + } + } + } + + /* Start polling loop */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + + mIsNfcEnabled = true; + } else { + mIsNfcEnabled = false; + } + + updateNfcOnSetting(); + + return isSuccess; + } + + private void updateNfcOnSetting() { + int state; + + if (mIsNfcEnabled) { + state = NFC_STATE_ENABLED; + } else { + state = NFC_STATE_DISABLED; + } + + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_ON, state); + } + + private void checkProperties() { + int value; + + /* LLCP LTO */ + value = Settings.System.getInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_LTO, + LLCP_LTO_DEFAULT_VALUE); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_LTO, value); + mManager.doSetProperties(PROPERTY_LLCP_LTO, value); + + /* LLCP MIU */ + value = Settings.System.getInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_MIU, + LLCP_MIU_DEFAULT_VALUE); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_MIU, value); + mManager.doSetProperties(PROPERTY_LLCP_MIU, value); + + /* LLCP WKS */ + value = Settings.System.getInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_WKS, + LLCP_WKS_DEFAULT_VALUE); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_WKS, value); + mManager.doSetProperties(PROPERTY_LLCP_WKS, value); + + /* LLCP OPT */ + value = Settings.System.getInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_OPT, + LLCP_OPT_DEFAULT_VALUE); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_OPT, value); + mManager.doSetProperties(PROPERTY_LLCP_OPT, value); + + /* NFC READER A */ + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_A, 1); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_A, + value); + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_A, value); + + /* NFC READER B */ + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_B, 1); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_B, + value); + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_B, value); + + /* NFC READER F */ + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_F, 1); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_F, + value); + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_F, value); + + /* NFC READER 15693 */ + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_15693, 1); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_15693, + value); + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_15693, value); + + /* NFC NFCIP */ + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_NFCIP, 1); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_NFCIP, + value); + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_NFCIP, value); + } + + public ILlcpConnectionlessSocket getLlcpConnectionlessInterface() throws RemoteException { + return mLlcpConnectionlessSocketService; + } + + public ILlcpSocket getLlcpInterface() throws RemoteException { + return mLlcpSocket; + } + + public ILlcpServiceSocket getLlcpServiceInterface() throws RemoteException { + return mLlcpServerSocketService; + } + + public INfcTag getNfcTagInterface() throws RemoteException { + return mNfcTagService; + } + + public int getOpenTimeout() throws RemoteException { + return mTimeout; + } + + public IP2pInitiator getP2pInitiatorInterface() throws RemoteException { + return mP2pInitiatorService; + } + + public IP2pTarget getP2pTargetInterface() throws RemoteException { + return mP2pTargetService; + } + + public String getProperties(String param) throws RemoteException { + int value; + + if (param == null) { + return "Wrong parameter"; + } + + if (param.equals(PROPERTY_LLCP_LTO_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_LLCP_LTO, 0); + } else if (param.equals(PROPERTY_LLCP_MIU_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_LLCP_MIU, 0); + } else if (param.equals(PROPERTY_LLCP_WKS_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_LLCP_WKS, 0); + } else if (param.equals(PROPERTY_LLCP_OPT_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_LLCP_OPT, 0); + } else if (param.equals(PROPERTY_NFC_DISCOVERY_A_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_A, 0); + } else if (param.equals(PROPERTY_NFC_DISCOVERY_B_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_B, 0); + } else if (param.equals(PROPERTY_NFC_DISCOVERY_F_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_F, 0); + } else if (param.equals(PROPERTY_NFC_DISCOVERY_NFCIP_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_NFCIP, 0); + } else if (param.equals(PROPERTY_NFC_DISCOVERY_15693_VALUE)) { + value = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_15693, 0); + } else { + return "Unknown property"; + } + + if (param.equals(PROPERTY_NFC_DISCOVERY_A_VALUE) + || param.equals(PROPERTY_NFC_DISCOVERY_B_VALUE) + || param.equals(PROPERTY_NFC_DISCOVERY_F_VALUE) + || param.equals(PROPERTY_NFC_DISCOVERY_NFCIP_VALUE) + || param.equals(PROPERTY_NFC_DISCOVERY_15693_VALUE)) { + if (value == 0) { + return "false"; + } else if (value == 1) { + return "true"; + } else { + return "Unknown Value"; + } + }else{ + return "" + value; + } + + } + + public int[] getSecureElementList() throws RemoteException { + int[] list = null; + if (mIsNfcEnabled == true) { + list = mManager.doGetSecureElementList(); + } + return list; + } + + public int getSelectedSecureElement() throws RemoteException { + return mSelectedSeId; + } + + public boolean isEnabled() throws RemoteException { + return mIsNfcEnabled; + } + + public int openP2pConnection() throws RemoteException { + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + mContext.enforceCallingPermission(android.Manifest.permission.NFC_RAW, + "NFC_RAW permission required to open NFC P2P connection"); + if (!mOpenPending) { + NativeP2pDevice device; + mOpenPending = true; + device = mManager.doOpenP2pConnection(mTimeout); + if (device != null) { + /* add device to the Hmap */ + mObjectMap.put(device.getHandle(), device); + return device.getHandle(); + } else { + mOpenPending = false; + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_BUSY; + } + + } + + public int openTagConnection() throws RemoteException { + NativeNfcTag tag; + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + mContext.enforceCallingPermission(android.Manifest.permission.NFC_RAW, + "NFC_RAW permission required to open NFC Tag connection"); + if (!mOpenPending) { + mOpenPending = true; + tag = mManager.doOpenTagConnection(mTimeout); + if (tag != null) { + mObjectMap.put(tag.getHandle(), tag); + return tag.getHandle(); + } else { + mOpenPending = false; + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + return ErrorCodes.ERROR_IO; + } + } else { + return ErrorCodes.ERROR_BUSY; + } + } + + public int selectSecureElement(int seId) throws RemoteException { + // Check if NFC is enabled + if (!mIsNfcEnabled) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + if (mSelectedSeId == seId) { + return ErrorCodes.ERROR_SE_ALREADY_SELECTED; + } + + if (mSelectedSeId != 0) { + return ErrorCodes.ERROR_SE_CONNECTED; + } + + mContext.enforceCallingPermission(android.Manifest.permission.NFC_ADMIN, + "NFC_ADMIN permission required to select NFC Secure Element"); + + mSelectedSeId = seId; + mManager.doSelectSecureElement(mSelectedSeId); + + /* Store that a secure element is selected */ + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NFC_SECURE_ELEMENT_ON, 1); + + /* Store the ID of the Secure Element Selected */ + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NFC_SECURE_ELEMENT_ID, mSelectedSeId); + + mNfcSecureElementState = 1; + + return ErrorCodes.SUCCESS; + + } + + public void setOpenTimeout(int timeout) throws RemoteException { + mContext.enforceCallingPermission(android.Manifest.permission.NFC_RAW, + "NFC_RAW permission required to set NFC connection timeout"); + mTimeout = timeout; + } + + public int setProperties(String param, String value) throws RemoteException { + mContext.enforceCallingPermission(android.Manifest.permission.NFC_ADMIN, + "NFC_ADMIN permission required to set NFC Properties"); + + if (isEnabled()) { + return ErrorCodes.ERROR_NFC_ON; + } + + int val; + + /* Check params validity */ + if (param == null || value == null) { + return ErrorCodes.ERROR_INVALID_PARAM; + } + + if (param.equals(PROPERTY_LLCP_LTO_VALUE)) { + val = Integer.parseInt(value); + + /* Check params */ + if (val > LLCP_LTO_MAX_VALUE) + return ErrorCodes.ERROR_INVALID_PARAM; + + /* Store value */ + Settings.System + .putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_LTO, val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_LLCP_LTO, val); + + } else if (param.equals(PROPERTY_LLCP_MIU_VALUE)) { + val = Integer.parseInt(value); + + /* Check params */ + if ((val < LLCP_MIU_DEFAULT_VALUE) || (val > LLCP_MIU_MAX_VALUE)) + return ErrorCodes.ERROR_INVALID_PARAM; + + /* Store value */ + Settings.System + .putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_MIU, val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_LLCP_MIU, val); + + } else if (param.equals(PROPERTY_LLCP_WKS_VALUE)) { + val = Integer.parseInt(value); + + /* Check params */ + if (val > LLCP_WKS_MAX_VALUE) + return ErrorCodes.ERROR_INVALID_PARAM; + + /* Store value */ + Settings.System + .putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_WKS, val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_LLCP_WKS, val); + + } else if (param.equals(PROPERTY_LLCP_OPT_VALUE)) { + val = Integer.parseInt(value); + + /* Check params */ + if (val > LLCP_OPT_MAX_VALUE) + return ErrorCodes.ERROR_INVALID_PARAM; + + /* Store value */ + Settings.System + .putInt(mContext.getContentResolver(), Settings.System.NFC_LLCP_OPT, val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_LLCP_OPT, val); + + } else if (param.equals(PROPERTY_NFC_DISCOVERY_A_VALUE)) { + + /* Check params */ + if (value.equals("true")) { + val = 1; + } else if (value.equals("false")) { + val = 0; + } else { + return ErrorCodes.ERROR_INVALID_PARAM; + } + /* Store value */ + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_A, + val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_A, val); + + } else if (param.equals(PROPERTY_NFC_DISCOVERY_B_VALUE)) { + + /* Check params */ + if (value.equals("true")) { + val = 1; + } else if (value.equals("false")) { + val = 0; + } else { + return ErrorCodes.ERROR_INVALID_PARAM; + } + + /* Store value */ + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_B, + val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_B, val); + + } else if (param.equals(PROPERTY_NFC_DISCOVERY_F_VALUE)) { + + /* Check params */ + if (value.equals("true")) { + val = 1; + } else if (value.equals("false")) { + val = 0; + } else { + return ErrorCodes.ERROR_INVALID_PARAM; + } + + /* Store value */ + Settings.System.putInt(mContext.getContentResolver(), Settings.System.NFC_DISCOVERY_F, + val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_F, val); + + } else if (param.equals(PROPERTY_NFC_DISCOVERY_15693_VALUE)) { + + /* Check params */ + if (value.equals("true")) { + val = 1; + } else if (value.equals("false")) { + val = 0; + } else { + return ErrorCodes.ERROR_INVALID_PARAM; + } + + /* Store value */ + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_15693, val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_15693, val); + + } else if (param.equals(PROPERTY_NFC_DISCOVERY_NFCIP_VALUE)) { + + /* Check params */ + if (value.equals("true")) { + val = 1; + } else if (value.equals("false")) { + val = 0; + } else { + return ErrorCodes.ERROR_INVALID_PARAM; + } + + /* Store value */ + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NFC_DISCOVERY_NFCIP, val); + + /* Update JNI */ + mManager.doSetProperties(PROPERTY_NFC_DISCOVERY_NFCIP, val); + } else { + return ErrorCodes.ERROR_INVALID_PARAM; + } + + return ErrorCodes.SUCCESS; + } + + // Reset all internals + private void reset() { + + // Clear tables + mObjectMap.clear(); + mSocketMap.clear(); + mRegisteredSocketList.clear(); + + // Reset variables + mLlcpLinkState = NfcManager.LLCP_LINK_STATE_DEACTIVATED; + mNbSocketCreated = 0; + mIsNfcEnabled = false; + mSelectedSeId = 0; + mTimeout = 0; + mNfcState = NFC_STATE_DISABLED; + mOpenPending = false; + } + + private Object findObject(int key) { + Object device = null; + + device = mObjectMap.get(key); + + return device; + } + + private void RemoveObject(int key) { + mObjectMap.remove(key); + } + + private Object findSocket(int key) { + Object socket = null; + + socket = mSocketMap.get(key); + + return socket; + } + + private void RemoveSocket(int key) { + mSocketMap.remove(key); + } + + private boolean CheckSocketSap(int sap) { + /* List of sockets registered */ + ListIterator<RegisteredSocket> it = mRegisteredSocketList.listIterator(); + + while (it.hasNext()) { + RegisteredSocket registeredSocket = it.next(); + + if (sap == registeredSocket.mSap) { + /* SAP already used */ + return false; + } + } + return true; + } + + private boolean CheckSocketOptions(int miu, int rw, int linearBufferlength) { + + if (rw > LLCP_RW_MAX_VALUE || miu < LLCP_MIU_DEFAULT_VALUE || linearBufferlength < miu) { + return false; + } + return true; + } + + private boolean CheckSocketServiceName(String sn) { + + /* List of sockets registered */ + ListIterator<RegisteredSocket> it = mRegisteredSocketList.listIterator(); + + while (it.hasNext()) { + RegisteredSocket registeredSocket = it.next(); + + if (sn.equals(registeredSocket.mServiceName)) { + /* Service Name already used */ + return false; + } + } + return true; + } + + private void RemoveRegisteredSocket(int nativeHandle) { + /* check if sockets are registered */ + ListIterator<RegisteredSocket> it = mRegisteredSocketList.listIterator(); + + while (it.hasNext()) { + RegisteredSocket registeredSocket = it.next(); + if (registeredSocket.mHandle == nativeHandle) { + /* remove the registered socket from the list */ + it.remove(); + Log.d(TAG, "socket removed"); + } + } + } + + /* + * RegisteredSocket class to store the creation request of socket until the + * LLCP link in not activated + */ + private class RegisteredSocket { + private int mType; + + private int mHandle; + + private int mSap; + + private int mMiu; + + private int mRw; + + private String mServiceName; + + private int mlinearBufferLength; + + RegisteredSocket(int type, int handle, int sap, String sn, int miu, int rw, + int linearBufferLength) { + mType = type; + mHandle = handle; + mSap = sap; + mServiceName = sn; + mRw = rw; + mMiu = miu; + mlinearBufferLength = linearBufferLength; + } + + RegisteredSocket(int type, int handle, int sap, int miu, int rw, int linearBufferLength) { + mType = type; + mHandle = handle; + mSap = sap; + mRw = rw; + mMiu = miu; + mlinearBufferLength = linearBufferLength; + } + + RegisteredSocket(int type, int handle, int sap) { + mType = type; + mHandle = handle; + mSap = sap; + } + } + + private BroadcastReceiver mNfcServiceReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Internal NFC Intent received"); + + /* LLCP Link deactivation */ + if (intent.getAction().equals(NfcManager.LLCP_LINK_STATE_CHANGED_ACTION)) { + mLlcpLinkState = intent.getIntExtra(NfcManager.LLCP_LINK_STATE_CHANGED_EXTRA, + NfcManager.LLCP_LINK_STATE_DEACTIVATED); + + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_DEACTIVATED) { + /* restart polling loop */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + } + + } + /* LLCP Link activation */ + else if (intent.getAction().equals( + NativeNfcManager.INTERNAL_LLCP_LINK_STATE_CHANGED_ACTION)) { + + mLlcpLinkState = intent.getIntExtra( + NativeNfcManager.INTERNAL_LLCP_LINK_STATE_CHANGED_EXTRA, + NfcManager.LLCP_LINK_STATE_DEACTIVATED); + + if (mLlcpLinkState == NfcManager.LLCP_LINK_STATE_ACTIVATED) { + /* check if sockets are registered */ + ListIterator<RegisteredSocket> it = mRegisteredSocketList.listIterator(); + + Log.d(TAG, "Nb socket resgistered = " + mRegisteredSocketList.size()); + + while (it.hasNext()) { + RegisteredSocket registeredSocket = it.next(); + + switch (registeredSocket.mType) { + case LLCP_SERVICE_SOCKET_TYPE: + Log.d(TAG, "Registered Llcp Service Socket"); + NativeLlcpServiceSocket serviceSocket; + + serviceSocket = mManager.doCreateLlcpServiceSocket( + registeredSocket.mSap, registeredSocket.mServiceName, + registeredSocket.mMiu, registeredSocket.mRw, + registeredSocket.mlinearBufferLength); + + if (serviceSocket != null) { + /* Add the socket into the socket map */ + mSocketMap.put(registeredSocket.mHandle, serviceSocket); + } else { + /* + * socket creation error - update the socket + * handle counter + */ + mGeneratedSocketHandle -= 1; + } + break; + + case LLCP_SOCKET_TYPE: + Log.d(TAG, "Registered Llcp Socket"); + NativeLlcpSocket clientSocket; + clientSocket = mManager.doCreateLlcpSocket(registeredSocket.mSap, + registeredSocket.mMiu, registeredSocket.mRw, + registeredSocket.mlinearBufferLength); + if (clientSocket != null) { + /* Add the socket into the socket map */ + mSocketMap.put(registeredSocket.mHandle, clientSocket); + } else { + /* + * socket creation error - update the socket + * handle counter + */ + mGeneratedSocketHandle -= 1; + } + break; + + case LLCP_CONNECTIONLESS_SOCKET_TYPE: + Log.d(TAG, "Registered Llcp Connectionless Socket"); + NativeLlcpConnectionlessSocket connectionlessSocket; + connectionlessSocket = mManager + .doCreateLlcpConnectionlessSocket(registeredSocket.mSap); + if (connectionlessSocket != null) { + /* Add the socket into the socket map */ + mSocketMap.put(registeredSocket.mHandle, connectionlessSocket); + } else { + /* + * socket creation error - update the socket + * handle counter + */ + mGeneratedSocketHandle -= 1; + } + break; + + } + } + + /* Remove all registered socket from the list */ + mRegisteredSocketList.clear(); + + /* Broadcast Intent Link LLCP activated */ + Intent LlcpLinkIntent = new Intent(); + LlcpLinkIntent.setAction(NfcManager.LLCP_LINK_STATE_CHANGED_ACTION); + + LlcpLinkIntent.putExtra(NfcManager.LLCP_LINK_STATE_CHANGED_EXTRA, + NfcManager.LLCP_LINK_STATE_ACTIVATED); + + Log.d(TAG, "Broadcasting LLCP activation"); + mContext.sendOrderedBroadcast(LlcpLinkIntent, + android.Manifest.permission.NFC_LLCP); + } + } + /* Target Deactivated */ + else if (intent.getAction().equals( + NativeNfcManager.INTERNAL_TARGET_DESELECTED_ACTION)) { + if(mOpenPending != false){ + mOpenPending = false; + } + /* Restart polling loop for notification */ + mManager.enableDiscovery(DISCOVERY_MODE_READER); + + } + } + }; +} diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index e3bae56..e1e54fc 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -207,7 +207,7 @@ public: virtual int32_t getMaxEventsPerSecond(); virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, const KeyEvent* keyEvent, uint32_t policyFlags); - virtual void pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType); + virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType); virtual bool checkInjectEventsPermissionNonReentrant( int32_t injectorPid, int32_t injectorUid); @@ -973,10 +973,8 @@ bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& i return consumed && ! error; } -void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType) { - if (windowType != InputWindow::TYPE_KEYGUARD) { - android_server_PowerManagerService_userActivity(eventTime, eventType); - } +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) { + android_server_PowerManagerService_userActivity(eventTime, eventType); } @@ -1288,6 +1286,25 @@ static void android_server_InputManager_nativeGetInputConfiguration(JNIEnv* env, env->SetIntField(configObj, gConfigurationClassInfo.navigation, config.navigation); } +static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env, + jclass clazz, jobject fromChannelObj, jobject toChannelObj) { + if (checkInputManagerUnitialized(env)) { + return false; + } + + sp<InputChannel> fromChannel = + android_view_InputChannel_getInputChannel(env, fromChannelObj); + sp<InputChannel> toChannel = + android_view_InputChannel_getInputChannel(env, toChannelObj); + + if (fromChannel == NULL || toChannel == NULL) { + return false; + } + + return gNativeInputManager->getInputManager()->getDispatcher()-> + transferTouchFocus(fromChannel, toChannel); +} + static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) { if (checkInputManagerUnitialized(env)) { return NULL; @@ -1336,6 +1353,8 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeGetInputDeviceIds }, { "nativeGetInputConfiguration", "(Landroid/content/res/Configuration;)V", (void*) android_server_InputManager_nativeGetInputConfiguration }, + { "nativeTransferTouchFocus", "(Landroid/view/InputChannel;Landroid/view/InputChannel;)Z", + (void*) android_server_InputManager_nativeTransferTouchFocus }, { "nativeDump", "()Ljava/lang/String;", (void*) android_server_InputManager_nativeDump }, }; diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java index 72c50fc..0d983b5 100644 --- a/telephony/java/com/android/internal/telephony/Connection.java +++ b/telephony/java/com/android/internal/telephony/Connection.java @@ -41,6 +41,8 @@ public abstract class Connection { INVALID_NUMBER, /* invalid dial string */ NUMBER_UNREACHABLE, /* cannot reach the peer */ INVALID_CREDENTIALS, /* invalid credentials */ + OUT_OF_NETWORK, /* calling from out of network is not allowed */ + SERVER_ERROR, /* server error */ TIMED_OUT, /* client timed out */ LOST_SIGNAL, LIMIT_EXCEEDED, /* eg GSM ACM limit exceeded */ diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java index 08194d4..e0eac74 100755 --- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java +++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java @@ -444,18 +444,23 @@ public class SipPhone extends SipPhoneBase { @Override public void hangup() throws CallStateException { synchronized (SipPhone.class) { - Log.v(LOG_TAG, "hang up call: " + getState() + ": " + this - + " on phone " + getPhone()); - CallStateException excp = null; - for (Connection c : connections) { - try { - c.hangup(); - } catch (CallStateException e) { - excp = e; + if (state.isAlive()) { + Log.d(LOG_TAG, "hang up call: " + getState() + ": " + this + + " on phone " + getPhone()); + CallStateException excp = null; + for (Connection c : connections) { + try { + c.hangup(); + } catch (CallStateException e) { + excp = e; + } } + if (excp != null) throw excp; + setState(State.DISCONNECTING); + } else { + Log.d(LOG_TAG, "hang up dead call: " + getState() + ": " + + this + " on phone " + getPhone()); } - if (excp != null) throw excp; - setState(State.DISCONNECTING); } } @@ -593,7 +598,10 @@ public class SipPhone extends SipPhoneBase { // set state to DISCONNECTED only when all conns are disconnected if (state != State.DISCONNECTED) { boolean allConnectionsDisconnected = true; + Log.v(LOG_TAG, "---check if all connections are disconnected: " + + connections.size()); for (Connection c : connections) { + Log.v(LOG_TAG, " state=" + c.getState() + ": " + c); if (c.getState() != State.DISCONNECTED) { allConnectionsDisconnected = false; break; @@ -636,6 +644,18 @@ public class SipPhone extends SipPhoneBase { } @Override + public void onCallEstablished(SipAudioCall call) { + onChanged(call); + if (mState == Call.State.ACTIVE) call.startAudio(); + } + + @Override + public void onCallHeld(SipAudioCall call) { + onChanged(call); + if (mState == Call.State.HOLDING) call.startAudio(); + } + + @Override public void onChanged(SipAudioCall call) { synchronized (SipPhone.class) { Call.State newState = getCallStateFrom(call); @@ -655,7 +675,6 @@ public class SipPhone extends SipPhoneBase { } foregroundCall.switchWith(ringingCall); } - if (newState == Call.State.ACTIVE) call.startAudio(); setState(newState); } mOwner.onConnectionStateChanged(SipConnection.this); @@ -773,11 +792,13 @@ public class SipPhone extends SipPhoneBase { public void hangup() throws CallStateException { synchronized (SipPhone.class) { Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": " - + ": on phone " + getPhone().getPhoneName()); + + mState + ": on phone " + getPhone().getPhoneName()); try { - if (mSipAudioCall != null) mSipAudioCall.endCall(); - setState(Call.State.DISCONNECTING); - setDisconnectCause(DisconnectCause.LOCAL); + if (mState.isAlive()) { + if (mSipAudioCall != null) mSipAudioCall.endCall(); + setState(Call.State.DISCONNECTING); + setDisconnectCause(DisconnectCause.LOCAL); + } } catch (SipException e) { throw new CallStateException("hangup(): " + e); } @@ -858,8 +879,13 @@ public class SipPhone extends SipPhoneBase { case SipErrorCode.INVALID_CREDENTIALS: onError(Connection.DisconnectCause.INVALID_CREDENTIALS); break; - case SipErrorCode.SOCKET_ERROR: + case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION: + onError(Connection.DisconnectCause.OUT_OF_NETWORK); + break; case SipErrorCode.SERVER_ERROR: + onError(Connection.DisconnectCause.SERVER_ERROR); + break; + case SipErrorCode.SOCKET_ERROR: case SipErrorCode.CLIENT_ERROR: default: Log.w(LOG_TAG, "error: " + SipErrorCode.toString(errorCode) diff --git a/test-runner/src/android/test/mock/MockContentResolver.java b/test-runner/src/android/test/mock/MockContentResolver.java index ab511f8..26eb8e4 100644 --- a/test-runner/src/android/test/mock/MockContentResolver.java +++ b/test-runner/src/android/test/mock/MockContentResolver.java @@ -75,6 +75,12 @@ public class MockContentResolver extends ContentResolver { /** @hide */ @Override protected IContentProvider acquireProvider(Context context, String name) { + return acquireExistingProvider(context, name); + } + + /** @hide */ + @Override + protected IContentProvider acquireExistingProvider(Context context, String name) { /* * Gets the content provider from the local map diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java index 65da210..dea549a 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java @@ -85,14 +85,19 @@ public class FileFilter { // http://b/issue?id=2889598 "http/tests/xmlhttprequest/simple-cross-origin-progress-events.html", // runs webcore into bad state // http://b/2982500 + "canvas/philip/tests/2d.drawImage.broken.html", // blocks test + // http://b/2982500 }; static void fillIgnoreResultList() { // This first block of tests are for features for which Android // should pass all tests. They are skipped only temporarily. // TODO: Fix these failing tests and remove them from this list. - ignoreResultList.add("fast/events/touch/basic-multi-touch-events.html"); // Requires multi-touch - ignoreResultList.add("fast/events/touch/touch-target.html"); // Requires multi-touch + ignoreResultList.add("fast/encoding/char-encoding.html"); // fails in Java HTTP stack, see http://b/issue?id=3047156 + ignoreResultList.add("fast/encoding/char-decoding.html"); // fails in Java HTTP stack, see http://b/issue?id=3047156 + ignoreResultList.add("fast/encoding/hanarei-blog32-fc2-com.html"); // fails in Java HTTP stack, see http://b/issue?id=3046986 + ignoreResultList.add("fast/encoding/mailto-always-utf-8.html"); // Requires waitForPolicyDelegate(), see http://b/issue?id=3043468 + ignoreResultList.add("fast/encoding/percent-escaping.html"); // fails in Java HTTP stack, see http://b/issue?id=3046984 ignoreResultList.add("http/tests/appcache/empty-manifest.html"); // flaky ignoreResultList.add("http/tests/appcache/fallback.html"); // http://b/issue?id=2713004 ignoreResultList.add("http/tests/appcache/foreign-iframe-main.html"); // flaky - skips states @@ -107,8 +112,13 @@ public class FileFilter { ignoreResultList.add("storage/transaction-error-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld() ignoreResultList.add("storage/transaction-success-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld() - // Expected failures due to unsupported features. + // Expected failures due to unsupported features or tests unsuitable for Android. + ignoreResultList.add("fast/encoding/char-decoding-mac.html"); // Mac-specific encodings (also marked Won't Fix in Chromium, bug 7388) + ignoreResultList.add("fast/encoding/char-encoding-mac.html"); // Mac-specific encodings (also marked Won't Fix in Chromium, bug 7388) + ignoreResultList.add("fast/encoding/idn-security.html"); // Mac-specific IDN checks (also marked Won't Fix in Chromium, bug 21814) + ignoreResultList.add("fast/events/touch/basic-multi-touch-events.html"); // Requires multi-touch gestures not supported by Android system ignoreResultList.add("fast/events/touch/touch-coords-in-zoom-and-scroll.html"); // Requires eventSender.zoomPageIn(),zoomPageOut() + ignoreResultList.add("fast/events/touch/touch-target.html"); // Requires multi-touch gestures not supported by Android system ignoreResultList.add("fast/workers"); // workers not supported ignoreResultList.add("http/tests/eventsource/workers"); // workers not supported ignoreResultList.add("http/tests/workers"); // workers not supported diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java index 40af8c0..19815fd 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -145,6 +145,10 @@ public class TestShellActivity extends Activity implements LayoutTestController // WebView::setJsFlags is noop in JSC build. mWebView.setJsFlags("--expose_gc"); + // Always send multitouch events to Webkit since the layout test + // is only for the Webkit not the browser's UI. + mWebView.setDeferMultiTouch(true); + mHandler = new AsyncHandler(); Intent intent = getIntent(); diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java index 716086b..383d782 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java @@ -31,6 +31,7 @@ public class WebViewEventSender implements EventSender { WebViewEventSender(WebView webView) { mWebView = webView; + mWebView.getSettings().setBuiltInZoomControls(true); mTouchPoints = new Vector<TouchPoint>(); } @@ -170,70 +171,128 @@ public class WebViewEventSender implements EventSender { } public void touchStart() { - // We only support single touch so examine the first touch point only. - // If multi touch is enabled in the future, we need to re-examine this to send - // all the touch points with the event. - TouchPoint tp = mTouchPoints.get(0); - - if (tp == null) { + final int numPoints = mTouchPoints.size(); + if (numPoints == 0) { return; } - tp.setDownTime(SystemClock.uptimeMillis()); - MotionEvent event = MotionEvent.obtain(tp.downTime(), tp.downTime(), - MotionEvent.ACTION_DOWN, tp.getX(), tp.getY(), mTouchMetaState); + int[] pointerIds = new int[numPoints]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints]; + long downTime = SystemClock.uptimeMillis(); + + for (int i = 0; i < numPoints; ++i) { + pointerIds[i] = mTouchPoints.get(i).getId(); + pointerCoords[i] = new MotionEvent.PointerCoords(); + pointerCoords[i].x = mTouchPoints.get(i).getX(); + pointerCoords[i].y = mTouchPoints.get(i).getY(); + mTouchPoints.get(i).setDownTime(downTime); + } + + MotionEvent event = MotionEvent.obtain(downTime, downTime, + MotionEvent.ACTION_DOWN, numPoints, pointerIds, pointerCoords, + mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0); + mWebView.onTouchEvent(event); } public void touchMove() { - TouchPoint tp = mTouchPoints.get(0); - - if (tp == null) { + final int numPoints = mTouchPoints.size(); + if (numPoints == 0) { return; } - if (!tp.hasMoved()) { + int[] pointerIds = new int[numPoints]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints]; + int numMovedPoints = 0; + for (int i = 0; i < numPoints; ++i) { + TouchPoint tp = mTouchPoints.get(i); + if (tp.hasMoved()) { + pointerIds[numMovedPoints] = mTouchPoints.get(i).getId(); + pointerCoords[i] = new MotionEvent.PointerCoords(); + pointerCoords[numMovedPoints].x = mTouchPoints.get(i).getX(); + pointerCoords[numMovedPoints].y = mTouchPoints.get(i).getY(); + ++numMovedPoints; + tp.setMoved(false); + } + } + + if (numMovedPoints == 0) { return; } - MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(), - MotionEvent.ACTION_MOVE, tp.getX(), tp.getY(), mTouchMetaState); + MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(), + SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, + numMovedPoints, pointerIds, pointerCoords, + mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0); mWebView.onTouchEvent(event); - - tp.setMoved(false); } public void touchEnd() { - TouchPoint tp = mTouchPoints.get(0); - - if (tp == null) { + final int numPoints = mTouchPoints.size(); + if (numPoints == 0) { return; } - MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(), - MotionEvent.ACTION_UP, tp.getX(), tp.getY(), mTouchMetaState); + int[] pointerIds = new int[numPoints]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints]; + + for (int i = 0; i < numPoints; ++i) { + pointerIds[i] = mTouchPoints.get(i).getId(); + pointerCoords[i] = new MotionEvent.PointerCoords(); + pointerCoords[i].x = mTouchPoints.get(i).getX(); + pointerCoords[i].y = mTouchPoints.get(i).getY(); + } + + MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(), + SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, + numPoints, pointerIds, pointerCoords, + mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0); mWebView.onTouchEvent(event); - if (tp.isReleased()) { - mTouchPoints.remove(0); + for (int i = numPoints - 1; i >= 0; --i) { // remove released points. + TouchPoint tp = mTouchPoints.get(i); + if (tp.isReleased()) { + mTouchPoints.remove(i); + } } } public void touchCancel() { - TouchPoint tp = mTouchPoints.get(0); - if (tp == null) { + final int numPoints = mTouchPoints.size(); + if (numPoints == 0) { return; } - if (tp.cancelled()) { - MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(), - MotionEvent.ACTION_CANCEL, tp.getX(), tp.getY(), mTouchMetaState); - mWebView.onTouchEvent(event); + int[] pointerIds = new int[numPoints]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints]; + long cancelTime = SystemClock.uptimeMillis(); + int numCanceledPoints = 0; + + for (int i = 0; i < numPoints; ++i) { + TouchPoint tp = mTouchPoints.get(i); + if (tp.cancelled()) { + pointerIds[numCanceledPoints] = mTouchPoints.get(i).getId(); + pointerCoords[numCanceledPoints] = new MotionEvent.PointerCoords(); + pointerCoords[numCanceledPoints].x = mTouchPoints.get(i).getX(); + pointerCoords[numCanceledPoints].y = mTouchPoints.get(i).getY(); + ++numCanceledPoints; + } + } + + if (numCanceledPoints == 0) { + return; } + + MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(), + SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, + numCanceledPoints, pointerIds, pointerCoords, + mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0); + + mWebView.onTouchEvent(event); } public void cancelTouchPoint(int id) { - TouchPoint tp = mTouchPoints.get(0); + TouchPoint tp = mTouchPoints.get(id); if (tp == null) { return; } @@ -242,14 +301,19 @@ public class WebViewEventSender implements EventSender { } public void addTouchPoint(int x, int y) { - mTouchPoints.add(new TouchPoint(contentsToWindowX(x), contentsToWindowY(y))); - if (mTouchPoints.size() > 1) { - Log.w(LOGTAG, "Adding more than one touch point, but multi touch is not supported!"); + final int numPoints = mTouchPoints.size(); + int id; + if (numPoints == 0) { + id = 0; + } else { + id = mTouchPoints.get(numPoints - 1).getId() + 1; } + + mTouchPoints.add(new TouchPoint(id, contentsToWindowX(x), contentsToWindowY(y))); } - public void updateTouchPoint(int id, int x, int y) { - TouchPoint tp = mTouchPoints.get(0); + public void updateTouchPoint(int i, int x, int y) { + TouchPoint tp = mTouchPoints.get(i); if (tp == null) { return; } @@ -276,7 +340,7 @@ public class WebViewEventSender implements EventSender { } public void releaseTouchPoint(int id) { - TouchPoint tp = mTouchPoints.get(0); + TouchPoint tp = mTouchPoints.get(id); if (tp == null) { return; } @@ -305,6 +369,7 @@ public class WebViewEventSender implements EventSender { private int mouseY; private class TouchPoint { + private int mId; private int mX; private int mY; private long mDownTime; @@ -312,7 +377,8 @@ public class WebViewEventSender implements EventSender { private boolean mMoved; private boolean mCancelled; - public TouchPoint(int x, int y) { + public TouchPoint(int id, int x, int y) { + mId = id; mX = x; mY = y; mReleased = false; @@ -332,6 +398,7 @@ public class WebViewEventSender implements EventSender { public void setMoved(boolean moved) { mMoved = moved; } public boolean hasMoved() { return mMoved; } + public int getId() { return mId; } public int getX() { return mX; } public int getY() { return mY; } diff --git a/tests/DumpRenderTree2/assets/run_layout_tests.py b/tests/DumpRenderTree2/assets/run_layout_tests.py index fd76e4a..303a054 100755 --- a/tests/DumpRenderTree2/assets/run_layout_tests.py +++ b/tests/DumpRenderTree2/assets/run_layout_tests.py @@ -12,6 +12,7 @@ import logging import optparse import os +import re import sys import subprocess import tempfile @@ -44,27 +45,33 @@ def main(options, args): os.system(cmd); # Run the tests in path - cmd = "adb shell am instrument " + adb_cmd = "adb" + if options.serial: + adb_cmd += " -s " + options.serial + cmd = adb_cmd + " shell am instrument " cmd += "-e class com.android.dumprendertree2.scriptsupport.Starter#startLayoutTests " cmd += "-e path \"" + path + "\" " - cmd +="-w com.android.dumprendertree2/com.android.dumprendertree2.scriptsupport.ScriptTestRunner" + cmd += "-w com.android.dumprendertree2/com.android.dumprendertree2.scriptsupport.ScriptTestRunner" logging.info("Running the tests...") - subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + (stdoutdata, stderrdata) = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + if re.search("^INSTRUMENTATION_STATUS_CODE: -1", stdoutdata, re.MULTILINE) != None: + logging.info("Failed to run the tests. Is DumpRenderTree2 installed on the device?") + return logging.info("Downloading the summaries...") # Download the txt summary to tmp folder summary_txt_tmp_path = os.path.join(tmpdir, SUMMARY_TXT) cmd = "rm -f " + summary_txt_tmp_path + ";" - cmd += "adb pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path - subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + cmd += adb_cmd + " pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path + subprocess.Popen(cmd, shell=True).wait() # Download the html summary to tmp folder details_html_tmp_path = os.path.join(tmpdir, DETAILS_HTML) cmd = "rm -f " + details_html_tmp_path + ";" - cmd += "adb pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path - subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + cmd += adb_cmd + " pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path + subprocess.Popen(cmd, shell=True).wait() # Print summary to console logging.info("All done.\n") @@ -82,5 +89,6 @@ if __name__ == "__main__": help="Show the results the host's default web browser, default=true") option_parser.add_option("", "--tests-root-directory", help="The directory from which to take the tests, default is external/webkit/LayoutTests in this checkout of the Android tree") + option_parser.add_option("-s", "--serial", default=None, help="Specify the serial number of device to run test on") options, args = option_parser.parse_args(); main(options, args); diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java index 93e6137..68bcf11 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java @@ -58,6 +58,7 @@ public class EventSenderImpl { public static class TouchPoint { WebView mWebView; + private int mId; private int mX; private int mY; private long mDownTime; @@ -65,12 +66,17 @@ public class EventSenderImpl { private boolean mMoved = false; private boolean mCancelled = false; - public TouchPoint(WebView webView, int x, int y) { + public TouchPoint(WebView webView, int id, int x, int y) { mWebView = webView; + mId = id; mX = scaleX(x); mY = scaleY(y); } + public int getId() { + return mId; + } + public int getX() { return mX; } @@ -136,9 +142,9 @@ public class EventSenderImpl { private Handler mEventSenderHandler = new Handler() { @Override public void handleMessage(Message msg) { - TouchPoint touchPoint; Bundle bundle; - KeyEvent event; + MotionEvent event; + long ts; switch (msg.what) { case MSG_ENABLE_DOM_UI_EVENT_LOGGING: @@ -171,82 +177,62 @@ public class EventSenderImpl { /** MOUSE */ case MSG_MOUSE_DOWN: - /** TODO: Implement */ + ts = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_DOWN, mMouseX, mMouseY, 0); + mWebView.onTouchEvent(event); break; case MSG_MOUSE_UP: - /** TODO: Implement */ + ts = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_UP, mMouseX, mMouseY, 0); + mWebView.onTouchEvent(event); break; case MSG_MOUSE_CLICK: - /** TODO: Implement */ + mouseDown(); + mouseUp(); break; case MSG_MOUSE_MOVE_TO: - int x = msg.arg1; - int y = msg.arg2; - - event = null; - if (x > mMouseX) { - event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); - } else if (x < mMouseX) { - event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); - } - if (event != null) { - mWebView.onKeyDown(event.getKeyCode(), event); - mWebView.onKeyUp(event.getKeyCode(), event); - } - - event = null; - if (y > mMouseY) { - event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN); - } else if (y < mMouseY) { - event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); - } - if (event != null) { - mWebView.onKeyDown(event.getKeyCode(), event); - mWebView.onKeyUp(event.getKeyCode(), event); - } - - mMouseX = x; - mMouseY = y; + mMouseX = msg.arg1; + mMouseY = msg.arg2; break; /** TOUCH */ case MSG_ADD_TOUCH_POINT: - getTouchPoints().add(new TouchPoint(mWebView, - msg.arg1, msg.arg2)); - if (getTouchPoints().size() > 1) { - Log.w(LOG_TAG + "::MSG_ADD_TOUCH_POINT", "Added more than one touch point"); + int numPoints = getTouchPoints().size(); + int id; + if (numPoints == 0) { + id = 0; + } else { + id = getTouchPoints().get(numPoints - 1).getId() + 1; } + getTouchPoints().add(new TouchPoint(mWebView, id, + msg.arg1, msg.arg2)); break; case MSG_TOUCH_START: - /** - * FIXME: At the moment we don't support multi-touch. Hence, we only examine - * the first touch point. In future this method will need rewriting. - */ if (getTouchPoints().isEmpty()) { return; } - touchPoint = getTouchPoints().get(0); - - touchPoint.setDownTime(SystemClock.uptimeMillis()); - executeTouchEvent(touchPoint, MotionEvent.ACTION_DOWN); + for (int i = 0; i < getTouchPoints().size(); ++i) { + getTouchPoints().get(i).setDownTime(SystemClock.uptimeMillis()); + } + executeTouchEvent(MotionEvent.ACTION_DOWN); break; case MSG_UPDATE_TOUCH_POINT: bundle = (Bundle)msg.obj; - int id = bundle.getInt("id"); - if (id >= getTouchPoints().size()) { + int index = bundle.getInt("id"); + if (index >= getTouchPoints().size()) { Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: " - + id); + + index); break; } - getTouchPoints().get(id).move(bundle.getInt("x"), bundle.getInt("y")); + getTouchPoints().get(index).move(bundle.getInt("x"), bundle.getInt("y")); break; case MSG_TOUCH_MOVE: @@ -257,13 +243,10 @@ public class EventSenderImpl { if (getTouchPoints().isEmpty()) { return; } - touchPoint = getTouchPoints().get(0); - - if (!touchPoint.hasMoved()) { - return; + executeTouchEvent(MotionEvent.ACTION_MOVE); + for (int i = 0; i < getTouchPoints().size(); ++i) { + getTouchPoints().get(i).resetHasMoved(); } - executeTouchEvent(touchPoint, MotionEvent.ACTION_MOVE); - touchPoint.resetHasMoved(); break; case MSG_CANCEL_TOUCH_POINT: @@ -284,11 +267,7 @@ public class EventSenderImpl { if (getTouchPoints().isEmpty()) { return; } - touchPoint = getTouchPoints().get(0); - - if (touchPoint.isCancelled()) { - executeTouchEvent(touchPoint, MotionEvent.ACTION_CANCEL); - } + executeTouchEvent(MotionEvent.ACTION_CANCEL); break; case MSG_RELEASE_TOUCH_POINT: @@ -309,12 +288,12 @@ public class EventSenderImpl { if (getTouchPoints().isEmpty()) { return; } - touchPoint = getTouchPoints().get(0); - - executeTouchEvent(touchPoint, MotionEvent.ACTION_UP); - if (touchPoint.isReleased()) { - getTouchPoints().remove(0); - touchPoint = null; + executeTouchEvent(MotionEvent.ACTION_UP); + // remove released points. + for (int i = getTouchPoints().size() - 1; i >= 0; --i) { + if (getTouchPoints().get(i).isReleased()) { + getTouchPoints().remove(i); + } } break; @@ -462,10 +441,48 @@ public class EventSenderImpl { return mTouchPoints; } - private void executeTouchEvent(TouchPoint touchPoint, int action) { - MotionEvent event = - MotionEvent.obtain(touchPoint.getDownTime(), SystemClock.uptimeMillis(), - action, touchPoint.getX(), touchPoint.getY(), mTouchMetaState); + private void executeTouchEvent(int action) { + int numPoints = getTouchPoints().size(); + int[] pointerIds = new int[numPoints]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints]; + + for (int i = 0; i < numPoints; ++i) { + boolean isNeeded = false; + switch(action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_UP: + isNeeded = true; + break; + case MotionEvent.ACTION_MOVE: + isNeeded = getTouchPoints().get(i).hasMoved(); + break; + case MotionEvent.ACTION_CANCEL: + isNeeded = getTouchPoints().get(i).isCancelled(); + break; + default: + Log.w(LOG_TAG + "::executeTouchEvent(),", "action not supported:" + action); + break; + } + + numPoints = 0; + if (isNeeded) { + pointerIds[numPoints] = getTouchPoints().get(i).getId(); + pointerCoords[numPoints] = new MotionEvent.PointerCoords(); + pointerCoords[numPoints].x = getTouchPoints().get(i).getX(); + pointerCoords[numPoints].y = getTouchPoints().get(i).getY(); + ++numPoints; + } + } + + if (numPoints == 0) { + return; + } + + MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).getDownTime(), + SystemClock.uptimeMillis(), action, + numPoints, pointerIds, pointerCoords, + mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0); + mWebView.onTouchEvent(event); } @@ -560,4 +577,4 @@ public class EventSenderImpl { return KeyEvent.KEYCODE_UNKNOWN; } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java index 4438811..4f9a737 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java @@ -223,10 +223,9 @@ public class FsUtils { try { return getHttpClient().execute(httpRequest, handler); } catch (IOException e) { - Log.e(LOG_TAG, "url=" + url, e); + Log.e(LOG_TAG, "getLayoutTestsDirContents(): HTTP GET failed for URL " + url); + return null; } - - return new LinkedList<String>(); } public static void closeInputStream(InputStream inputStream) { @@ -248,4 +247,4 @@ public class FsUtils { Log.e(LOG_TAG, "Couldn't close stream!", e); } } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java index 30d255a..97d7cca 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java @@ -377,11 +377,14 @@ public class LayoutTestsExecutor extends Activity { webView.setTouchInterval(-1); webView.clearCache(true); + webView.setDeferMultiTouch(true); WebSettings webViewSettings = webView.getSettings(); webViewSettings.setAppCacheEnabled(true); webViewSettings.setAppCachePath(getApplicationContext().getCacheDir().getPath()); - webViewSettings.setAppCacheMaxSize(Long.MAX_VALUE); + // Use of larger values causes unexplained AppCache database corruption. + // TODO: Investigate what's really going on here. + webViewSettings.setAppCacheMaxSize(100 * 1024 * 1024); webViewSettings.setJavaScriptEnabled(true); webViewSettings.setJavaScriptCanOpenWindowsAutomatically(true); webViewSettings.setSupportMultipleWindows(true); diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java index 17e19d0..f42dc86 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java @@ -267,7 +267,7 @@ public class ManagerService extends Service { bytes = FsUtils.readDataFromUrl(FileFilter.getUrl(relativePath)); } - mLastExpectedResultPathFetched = relativePath; + mLastExpectedResultPathFetched = bytes == null ? null : relativePath; return bytes; } diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java index 25c5ad5..8d01a53 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java @@ -539,7 +539,11 @@ public class Summarizer { String textSource = result.getExpectedTextResultPath(); String imageSource = result.getExpectedImageResultPath(); - if (textSource != null) { + if (textSource == null) { + // Show if a text result is missing. We may want to revisit this decision when we add + // support for image results. + html.append("<span class=\"source\">Expected textual result missing</span>"); + } else { html.append("<span class=\"source\">Expected textual result from: "); html.append("<a href=\"" + ForwarderManager.getHostSchemePort(false) + "LayoutTests/" + textSource + "\""); diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java index e0f1450..0e7d47a 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java @@ -21,6 +21,7 @@ import android.os.Message; import java.io.File; import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; /** * A Thread that is responsible for generating a lists of tests to run. @@ -35,7 +36,7 @@ public class TestsListPreloaderThread extends Thread { private FileFilter mFileFilter; /** - * A relative path to the folder with the tests we want to run or particular test. + * A relative path to the directory with the tests we want to run or particular test. * Used up to and including preloadTests(). */ private String mRelativePath; @@ -67,40 +68,44 @@ public class TestsListPreloaderThread extends Thread { } /** - * Loads all the tests from the given folders and all the subfolders + * Loads all the tests from the given directories and all the subdirectories * into mTestsList. * * @param dirRelativePath */ - private void loadTestsFromUrl(String dirRelativePath) { - LinkedList<String> foldersList = new LinkedList<String>(); - foldersList.add(dirRelativePath); + private void loadTestsFromUrl(String rootRelativePath) { + LinkedList<String> directoriesList = new LinkedList<String>(); + directoriesList.add(rootRelativePath); String relativePath; String itemName; - while (!foldersList.isEmpty()) { - relativePath = foldersList.removeFirst(); + while (!directoriesList.isEmpty()) { + relativePath = directoriesList.removeFirst(); - for (String folderRelativePath : FsUtils.getLayoutTestsDirContents(relativePath, - false, true)) { - itemName = new File(folderRelativePath).getName(); - if (FileFilter.isTestDir(itemName)) { - foldersList.add(folderRelativePath); + List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, true); + if (dirRelativePaths != null) { + for (String dirRelativePath : dirRelativePaths) { + itemName = new File(dirRelativePath).getName(); + if (FileFilter.isTestDir(itemName)) { + directoriesList.add(dirRelativePath); + } } } - for (String testRelativePath : FsUtils.getLayoutTestsDirContents(relativePath, - false, false)) { - itemName = new File(testRelativePath).getName(); - if (FileFilter.isTestFile(itemName)) { - /** We chose to skip all the tests that are expected to crash. */ - if (!mFileFilter.isCrash(testRelativePath)) { - mTestsList.add(testRelativePath); - } else { - /** - * TODO: Summarizer is now in service - figure out how to send the info. - * Previously: mSummarizer.addSkippedTest(relativePath); - */ + List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, false); + if (testRelativePaths != null) { + for (String testRelativePath : testRelativePaths) { + itemName = new File(testRelativePath).getName(); + if (FileFilter.isTestFile(itemName)) { + /** We choose to skip all the tests that are expected to crash. */ + if (!mFileFilter.isCrash(testRelativePath)) { + mTestsList.add(testRelativePath); + } else { + /** + * TODO: Summarizer is now in service - figure out how to send the info. + * Previously: mSummarizer.addSkippedTest(relativePath); + */ + } } } } diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java index f835b6a..3d2b98b 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java @@ -87,20 +87,26 @@ public class TextResult extends AbstractResult { @Override public ResultCode getResultCode() { - if (mResultCode != null) { - return mResultCode; - } - - if (mExpectedResult == null) { - mResultCode = AbstractResult.ResultCode.NO_EXPECTED_RESULT; - } else if (!mExpectedResult.equals(mActualResult)) { - mResultCode = AbstractResult.ResultCode.RESULTS_DIFFER; - } else { - mResultCode = AbstractResult.ResultCode.RESULTS_MATCH; + if (mResultCode == null) { + mResultCode = resultsMatch() ? AbstractResult.ResultCode.RESULTS_MATCH + : AbstractResult.ResultCode.RESULTS_DIFFER; } return mResultCode; } + private boolean resultsMatch() { + assert mExpectedResult != null; + assert mActualResult != null; + // Trim leading and trailing empty lines, as other WebKit platforms do. + String leadingEmptyLines = "^\\n+"; + String trailingEmptyLines = "\\n+$"; + String trimmedExpectedResult = mExpectedResult.replaceFirst(leadingEmptyLines, "") + .replaceFirst(trailingEmptyLines, ""); + String trimmedActualResult = mActualResult.replaceFirst(leadingEmptyLines, "") + .replaceFirst(trailingEmptyLines, ""); + return trimmedExpectedResult.equals(trimmedActualResult); + } + @Override public boolean didCrash() { return false; @@ -125,7 +131,7 @@ public class TextResult extends AbstractResult { public String getActualTextResult() { String additionalTextResultString = getAdditionalTextOutputString(); if (additionalTextResultString != null) { - return additionalTextResultString+ mActualResult; + return additionalTextResultString + mActualResult; } return mActualResult; @@ -159,11 +165,16 @@ public class TextResult extends AbstractResult { @Override public void setExpectedTextResult(String expectedResult) { - mExpectedResult = expectedResult; + // For text results, we use an empty string for the expected result when none is + // present, as other WebKit platforms do. + mExpectedResult = expectedResult == null ? "" : expectedResult; } @Override public String getDiffAsHtml() { + assert mExpectedResult != null; + assert mActualResult != null; + StringBuilder html = new StringBuilder(); html.append("<table class=\"visual_diff\">"); html.append(" <tr class=\"headers\">"); @@ -172,11 +183,7 @@ public class TextResult extends AbstractResult { html.append(" <td colspan=\"2\">Actual result:</td>"); html.append(" </tr>"); - if (mExpectedResult == null || mActualResult == null) { - appendNullsHtml(html); - } else { - appendDiffHtml(html); - } + appendDiffHtml(html); html.append(" <tr class=\"footers\">"); html.append(" <td colspan=\"2\"></td>"); @@ -201,36 +208,15 @@ public class TextResult extends AbstractResult { VisualDiffUtils.generateExpectedResultLines(diffs, expectedLineNums, expectedLines); VisualDiffUtils.generateActualResultLines(diffs, actualLineNums, actualLines); + // TODO: We should use a map for each line number and lines pair. + assert expectedLines.size() == expectedLineNums.size(); + assert actualLines.size() == actualLineNums.size(); + assert expectedLines.size() == actualLines.size(); html.append(VisualDiffUtils.getHtml(expectedLineNums, expectedLines, actualLineNums, actualLines)); } - private void appendNullsHtml(StringBuilder html) { - /** TODO: Create a separate row for each line of not null result */ - html.append(" <tr class=\"results\">"); - html.append(" <td class=\"line_count\">"); - html.append(" </td>"); - html.append(" <td class=\"line\">"); - if (mExpectedResult == null) { - html.append("Expected result was NULL"); - } else { - html.append(mExpectedResult.replace("\n", "<br />")); - } - html.append(" </td>"); - html.append(" <td class=\"space\"></td>"); - html.append(" <td class=\"line_count\">"); - html.append(" </td>"); - html.append(" <td class=\"line\">"); - if (mActualResult == null) { - html.append("Actual result was NULL"); - } else { - html.append(mActualResult.replace("\n", "<br />")); - } - html.append(" </td>"); - html.append(" </tr>"); - } - @Override public TestType getType() { return TestType.TEXT; diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java index 26bf75c..d7f7313 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java @@ -68,35 +68,38 @@ public class VisualDiffUtils { String line = ""; int i = 1; - for (diff_match_patch.Diff diff : diffs) { + diff_match_patch.Diff diff; + int size = diffs.size(); + boolean isLastDiff; + for (int j = 0; j < size; j++) { + diff = diffs.get(j); + isLastDiff = j == size - 1; switch (diff.operation) { case DELETE: - line = processDiff(diff, lineNums, lines, line, i, delSpan); + line = processDiff(diff, lineNums, lines, line, i, delSpan, isLastDiff); if (line.equals("")) { i++; } break; case INSERT: - if (diff.text.endsWith("\n")) { - lineNums.add(DONT_PRINT_LINE_NUMBER); - lines.add(""); + // If the line is currently empty and this insertion is the entire line, the + // expected line is absent, so it has no line number. + if (diff.text.endsWith("\n") || isLastDiff) { + lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++); + lines.add(line); + line = ""; } break; case EQUAL: - line = processDiff(diff, lineNums, lines, line, i, eqlSpan); + line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff); if (line.equals("")) { i++; } break; } } - - if (!line.isEmpty()) { - lines.add(line); - lineNums.add(i); - } } public static void generateActualResultLines(LinkedList<diff_match_patch.Diff> diffs, @@ -106,35 +109,38 @@ public class VisualDiffUtils { String line = ""; int i = 1; - for (diff_match_patch.Diff diff : diffs) { + diff_match_patch.Diff diff; + int size = diffs.size(); + boolean isLastDiff; + for (int j = 0; j < size; j++) { + diff = diffs.get(j); + isLastDiff = j == size - 1; switch (diff.operation) { case INSERT: - line = processDiff(diff, lineNums, lines, line, i, insSpan); + line = processDiff(diff, lineNums, lines, line, i, insSpan, isLastDiff); if (line.equals("")) { i++; } break; case DELETE: - if (diff.text.endsWith("\n")) { - lineNums.add(DONT_PRINT_LINE_NUMBER); - lines.add(""); + // If the line is currently empty and deletion is the entire line, the + // actual line is absent, so it has no line number. + if (diff.text.endsWith("\n") || isLastDiff) { + lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++); + lines.add(line); + line = ""; } break; case EQUAL: - line = processDiff(diff, lineNums, lines, line, i, eqlSpan); + line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff); if (line.equals("")) { i++; } break; } } - - if (!line.isEmpty()) { - lines.add(line); - lineNums.add(i); - } } /** @@ -147,15 +153,16 @@ public class VisualDiffUtils { * @param line * @param i * @param begSpan + * @param forceOutputLine Force the current line to be output * @return */ public static String processDiff(diff_match_patch.Diff diff, LinkedList<Integer> lineNums, - LinkedList<String> lines, String line, int i, String begSpan) { + LinkedList<String> lines, String line, int i, String begSpan, boolean forceOutputLine) { String endSpan = "</span>"; String br = " "; - if (diff.text.endsWith("\n")) { - lineNums.add(i++); + if (diff.text.endsWith("\n") || forceOutputLine) { + lineNums.add(i); /** TODO: Think of better way to replace stuff */ line += begSpan + diff.text.replace(" ", " ") + endSpan + br; @@ -204,4 +211,4 @@ public class VisualDiffUtils { } return html.toString(); } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java index 086ff59..224509d 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java @@ -40,15 +40,10 @@ public class AdbUtils { * remote machine. This can be achieved by calling configureSocket() * * @return a socket that can be configured to link to remote machine + * @throws IOException */ - public static Socket createSocket() { - Socket socket = null; - try { - socket = new Socket(ADB_HOST, ADB_PORT); - } catch (IOException e) { - Log.e(LOG_TAG, "Creation failed.", e); - } - return socket; + public static Socket createSocket() throws IOException{ + return new Socket(ADB_HOST, ADB_PORT); } /** @@ -72,9 +67,9 @@ public class AdbUtils { outputStream.write(cmd.getBytes()); int read = inputStream.read(buf); if (read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) { - Log.w(LOG_TAG, "adb cmd faild."); + Log.w(LOG_TAG, "adb cmd failed."); return false; } return true; } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java index 4f01dae..f19cd41 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java @@ -93,7 +93,8 @@ public class ConnectionHandler { private OnFinishedCallback mOnFinishedCallback; - public ConnectionHandler(String remoteMachineIp, int port, Socket fromSocket, Socket toSocket) { + public ConnectionHandler(String remoteMachineIp, int port, Socket fromSocket, Socket toSocket) + throws IOException { mRemoteMachineIpAddress = remoteMachineIp; mPort = port; @@ -105,14 +106,12 @@ public class ConnectionHandler { mToSocketInputStream = mToSocket.getInputStream(); mFromSocketOutputStream = mFromSocket.getOutputStream(); mToSocketOutputStream = mToSocket.getOutputStream(); - if (!AdbUtils.configureConnection(mToSocketInputStream, mToSocketOutputStream, - mRemoteMachineIpAddress, mPort)) { - throw new IOException("Configuring socket failed!"); - } + AdbUtils.configureConnection(mToSocketInputStream, mToSocketOutputStream, + mRemoteMachineIpAddress, mPort); } catch (IOException e) { Log.e(LOG_TAG, "Unable to start ConnectionHandler", e); closeStreams(); - return; + throw e; } mFromToPipe = new SocketPipeThread(mFromSocketInputStream, mToSocketOutputStream); @@ -170,4 +169,4 @@ public class ConnectionHandler { } } } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java index b361a89..ce22fa0 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java @@ -61,7 +61,6 @@ public class Forwarder extends Thread { public void run() { while (true) { Socket localSocket; - Socket remoteSocket; try { localSocket = mServerSocket.accept(); } catch (IOException e) { @@ -70,24 +69,28 @@ public class Forwarder extends Thread { break; } - remoteSocket = AdbUtils.createSocket(); - - if (remoteSocket == null) { + Socket remoteSocket = null; + final ConnectionHandler connectionHandler; + try { + remoteSocket = AdbUtils.createSocket(); + connectionHandler = new ConnectionHandler( + mRemoteMachineIpAddress, mPort, localSocket, remoteSocket); + } catch (IOException exception) { try { localSocket.close(); } catch (IOException e) { Log.e(LOG_TAG, "mPort=" + mPort, e); } - - Log.e(LOG_TAG, "run(): mPort= " + mPort + " Failed to start forwarding from " + - localSocket); + if (remoteSocket != null) { + try { + remoteSocket.close(); + } catch (IOException e) { + Log.e(LOG_TAG, "mPort=" + mPort, e); + } + } continue; } - final ConnectionHandler connectionHandler = - new ConnectionHandler(mRemoteMachineIpAddress, mPort, localSocket, - remoteSocket); - /** * We have to close the sockets after the ConnectionHandler finishes, so we * don't get "Too may open files" exception. We also remove the ConnectionHandler @@ -126,4 +129,4 @@ public class Forwarder extends Thread { Log.e(LOG_TAG, "mPort=" + mPort, e); } } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java index 35de88a..5de69a7 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java @@ -45,6 +45,7 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import android.widget.Toast; import java.io.File; import java.util.ArrayList; @@ -69,6 +70,9 @@ public class DirListActivity extends ListActivity { private static final int MSG_LOADED_ITEMS = 0; private static final int MSG_SHOW_PROGRESS_DIALOG = 1; + private static final CharSequence NO_RESPONSE_MESSAGE = + "No response from host when getting directory contents. Is the host server running?"; + /** Initialized lazily before first sProgressDialog.show() */ private static ProgressDialog sProgressDialog; @@ -349,13 +353,18 @@ public class DirListActivity extends ListActivity { @Override public void handleMessage(Message msg) { if (msg.what == MSG_LOADED_ITEMS) { - setListAdapter(new DirListAdapter(DirListActivity.this, - (ListItem[])msg.obj)); - delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG); setTitle(shortenTitle(mCurrentDirPath)); + delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG); if (sProgressDialog != null) { sProgressDialog.dismiss(); } + if (msg.obj == null) { + Toast.makeText(DirListActivity.this, NO_RESPONSE_MESSAGE, + Toast.LENGTH_LONG).show(); + } else { + setListAdapter(new DirListAdapter(DirListActivity.this, + (ListItem[])msg.obj)); + } } } }).start(); @@ -389,15 +398,21 @@ public class DirListActivity extends ListActivity { List<ListItem> subDirs = new ArrayList<ListItem>(); List<ListItem> subFiles = new ArrayList<ListItem>(); - for (String dirRelativePath : FsUtils.getLayoutTestsDirContents(dirPath, false, - true)) { + List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, true); + if (dirRelativePaths == null) { + return null; + } + for (String dirRelativePath : dirRelativePaths) { if (FileFilter.isTestDir(new File(dirRelativePath).getName())) { subDirs.add(new ListItem(dirRelativePath, true)); } } - for (String testRelativePath : FsUtils.getLayoutTestsDirContents(dirPath, false, - false)) { + List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, false); + if (testRelativePaths == null) { + return null; + } + for (String testRelativePath : testRelativePaths) { if (FileFilter.isTestFile(new File(testRelativePath).getName())) { subFiles.add(new ListItem(testRelativePath, false)); } diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java index e31711e..7e69ee4 100644 --- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java @@ -60,6 +60,27 @@ public class StatusBarTest extends TestActivity } private Test[] mTests = new Test[] { + new Test("Double Remove") { + public void run() { + Log.d(TAG, "set 0"); + mStatusBarManager.setIcon("speakerphone", R.drawable.stat_sys_phone, 0); + Log.d(TAG, "remove 1"); + mStatusBarManager.removeIcon("tty"); + + SystemClock.sleep(1000); + + Log.d(TAG, "set 1"); + mStatusBarManager.setIcon("tty", R.drawable.stat_sys_phone, 0); + if (false) { + Log.d(TAG, "set 2"); + mStatusBarManager.setIcon("tty", R.drawable.stat_sys_phone, 0); + } + Log.d(TAG, "remove 2"); + mStatusBarManager.removeIcon("tty"); + Log.d(TAG, "set 3"); + mStatusBarManager.setIcon("speakerphone", R.drawable.stat_sys_phone, 0); + } + }, new Test("Hide (FLAG_FULLSCREEN)") { public void run() { Window win = getWindow(); @@ -147,6 +168,24 @@ public class StatusBarTest extends TestActivity }, 3000); } }, + new Test("Disable Navigation") { + public void run() { + mStatusBarManager.disable(StatusBarManager.DISABLE_NAVIGATION); + } + }, + new Test("Disable everything in 3 sec") { + public void run() { + mHandler.postDelayed(new Runnable() { + public void run() { + mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND + | StatusBarManager.DISABLE_NOTIFICATION_ICONS + | StatusBarManager.DISABLE_NOTIFICATION_ALERTS + | StatusBarManager.DISABLE_SYSTEM_INFO + | StatusBarManager.DISABLE_NAVIGATION); + } + }, 3000); + } + }, new Test("Enable everything") { public void run() { mStatusBarManager.disable(0); diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 135a633..b27ce0e 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -25,15 +25,11 @@ include $(CLEAR_VARS) # We need to process the framework classes.jar file, but we can't # depend directly on it (private vars won't be inherited correctly). # So, we depend on framework's BUILT file. -built_framework_dep := \ - $(call intermediates-dir-for,JAVA_LIBRARIES,framework)/javalib.jar -built_framework_classes := \ - $(call intermediates-dir-for,JAVA_LIBRARIES,framework)/classes.jar +built_framework_dep := $(call java-lib-deps,framework) +built_framework_classes := $(call java-lib-files,framework) -built_core_dep := \ - $(call intermediates-dir-for,JAVA_LIBRARIES,core)/javalib.jar -built_core_classes := \ - $(call intermediates-dir-for,JAVA_LIBRARIES,core)/classes.jar +built_core_dep := $(call java-lib-deps,core) +built_core_classes := $(call java-lib-files,core) built_layoutlib_create_jar := $(call intermediates-dir-for, \ JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java index cfab90a..d89dba9 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java @@ -43,6 +43,12 @@ public class BridgeContentResolver extends ContentResolver { } @Override + public IContentProvider acquireExistingProvider(Context c, String name) { + // ignore + return null; + } + + @Override public boolean releaseProvider(IContentProvider icp) { // ignore return false; diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java index e0dc55f..adb693d 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java @@ -17,6 +17,8 @@ package com.android.layoutlib.bridge; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; import junit.framework.TestCase; @@ -26,7 +28,8 @@ public class TestClassReplacement extends TestCase { // TODO: we want to test all the classes. For now only Paint passes the tests. // final String[] classes = CreateInfo.RENAMED_CLASSES; final String[] classes = new String[] { - "android.graphics.Paint", "android.graphics._Original_Paint" + "android.graphics.Paint", "android.graphics._Original_Paint", + "android.graphics.Canvas", "android.graphics._Original_Canvas", }; final int count = classes.length; for (int i = 0 ; i < count ; i += 2) { @@ -52,12 +55,21 @@ public class TestClassReplacement extends TestCase { Method[] oldClassMethods = oldClass.getDeclaredMethods(); for (Method oldMethod : oldClassMethods) { - // we ignore anything that starts with native + // we ignore anything that starts with native. This is because the class we are looking + // at has already been modified to remove the native modifiers. if (oldMethod.getName().startsWith("native")) { continue; } + + // or static and private + int privateStatic = Modifier.STATIC | Modifier.PRIVATE; + if ((oldMethod.getModifiers() & privateStatic) == privateStatic) { + continue; + } + boolean found = false; for (Method newMethod : newClassMethods) { + if (compareMethods(newClass, newMethod, oldClass, oldMethod)) { found = true; break; @@ -65,7 +77,31 @@ public class TestClassReplacement extends TestCase { } if (found == false) { - fail(String.format("Unable to find %1$s", oldMethod.toGenericString())); + // compute a full class name that's long but not too long. + StringBuilder sb = new StringBuilder(oldMethod.getName() + "("); + Type[] params = oldMethod.getGenericParameterTypes(); + for (int j = 0; j < params.length; j++) { + if (params[j] instanceof Class) { + Class theClass = (Class)params[j]; + sb.append(theClass.getName()); + int dimensions = 0; + while (theClass.isArray()) { + dimensions++; + theClass = theClass.getComponentType(); + } + for (int i = 0; i < dimensions; i++) { + sb.append("[]"); + } + + } else { + sb.append(params[j].toString()); + } + if (j < (params.length - 1)) + sb.append(","); + } + sb.append(")"); + + fail(String.format("Missing %1$s.%2$s", newClass.getName(), sb.toString())); } } diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java index 4851a46..dfa6841 100644 --- a/voip/java/android/net/rtp/AudioCodec.java +++ b/voip/java/android/net/rtp/AudioCodec.java @@ -81,7 +81,7 @@ public class AudioCodec { public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null); // TODO: add rest of the codecs when the native part is done. - private static final AudioCodec[] sCodecs = {PCMU, PCMA}; + private static final AudioCodec[] sCodecs = {GSM, PCMU, PCMA}; private AudioCodec(int type, String rtpmap, String fmtp) { this.type = type; diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java index 2135fcb..7c1234a 100644 --- a/voip/java/android/net/sip/SipAudioCall.java +++ b/voip/java/android/net/sip/SipAudioCall.java @@ -46,7 +46,7 @@ import java.util.Map; * Class that handles an audio call over SIP. */ /** @hide */ -public class SipAudioCall extends SipSessionAdapter { +public class SipAudioCall { private static final String TAG = SipAudioCall.class.getSimpleName(); private static final boolean RELEASE_SOCKET = true; private static final boolean DONT_RELEASE_SOCKET = false; @@ -530,7 +530,7 @@ public class SipAudioCall extends SipSessionAdapter { * will be called. * * @param callee the SIP profile to make the call to - * @param sipManager the {@link SipManager} object to help make call with + * @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. * @see Listener.onError @@ -538,16 +538,12 @@ public class SipAudioCall extends SipSessionAdapter { * call */ public synchronized void makeCall(SipProfile peerProfile, - SipManager sipManager, int timeout) throws SipException { - SipSession s = mSipSession = sipManager.createSipSession( - mLocalProfile, createListener()); - if (s == null) { - throw new SipException( - "Failed to create SipSession; network available?"); - } + SipSession sipSession, int timeout) throws SipException { + mSipSession = sipSession; try { mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); - s.makeCall(peerProfile, createOffer().encode(), timeout); + sipSession.setListener(createListener()); + sipSession.makeCall(peerProfile, createOffer().encode(), timeout); } catch (IOException e) { throw new SipException("makeCall()", e); } diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java index 7496d28..eb7a1ae 100644 --- a/voip/java/android/net/sip/SipErrorCode.java +++ b/voip/java/android/net/sip/SipErrorCode.java @@ -58,6 +58,9 @@ public class SipErrorCode { /** When data connection is lost. */ public static final int DATA_CONNECTION_LOST = -10; + /** Cross-domain authentication required. */ + public static final int CROSS_DOMAIN_AUTHENTICATION = -11; + public static String toString(int errorCode) { switch (errorCode) { case NO_ERROR: @@ -82,6 +85,8 @@ public class SipErrorCode { return "IN_PROGRESS"; case DATA_CONNECTION_LOST: return "DATA_CONNECTION_LOST"; + case CROSS_DOMAIN_AUTHENTICATION: + return "CROSS_DOMAIN_AUTHENTICATION"; default: return "UNKNOWN"; } diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java index 5976a04..59631c1 100644 --- a/voip/java/android/net/sip/SipManager.java +++ b/voip/java/android/net/sip/SipManager.java @@ -269,7 +269,12 @@ public class SipManager { throws SipException { SipAudioCall call = new SipAudioCall(mContext, localProfile); call.setListener(listener); - call.makeCall(peerProfile, this, timeout); + SipSession s = createSipSession(localProfile, null); + if (s == null) { + throw new SipException( + "Failed to create SipSession; network available?"); + } + call.makeCall(peerProfile, s, timeout); return call; } diff --git a/services/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java index 050eddc..050eddc 100644 --- a/services/java/com/android/server/sip/SipHelper.java +++ b/voip/java/com/android/server/sip/SipHelper.java diff --git a/services/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java index 3f43e1c..0ff5586 100644 --- a/services/java/com/android/server/sip/SipService.java +++ b/voip/java/com/android/server/sip/SipService.java @@ -40,6 +40,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; @@ -90,12 +91,14 @@ public final class SipService extends ISipService.Stub { private ConnectivityReceiver mConnectivityReceiver; /** - * Creates a {@code SipService} instance. Returns null if SIP API is not - * supported. + * Starts the SIP service. Do nothing if the SIP API is not supported on the + * device. */ - public static SipService create(Context context) { - return (SipManager.isApiSupported(context) ? new SipService(context) - : null); + public static void start(Context context) { + if (SipManager.isApiSupported(context)) { + ServiceManager.addService("sip", new SipService(context)); + Log.i(TAG, "SIP service started"); + } } private SipService(Context context) { diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java index fa3f64a..4321d7b 100644 --- a/services/java/com/android/server/sip/SipSessionGroup.java +++ b/voip/java/com/android/server/sip/SipSessionGroup.java @@ -28,7 +28,6 @@ import android.net.sip.ISipSessionListener; import android.net.sip.SipErrorCode; import android.net.sip.SipProfile; import android.net.sip.SipSession; -import android.net.sip.SipSessionAdapter; import android.text.TextUtils; import android.util.Log; @@ -82,7 +81,6 @@ class SipSessionGroup implements SipListener { private static final boolean DEBUG = true; private static final boolean DEBUG_PING = DEBUG && false; private static final String ANONYMOUS = "anonymous"; - private static final String SERVER_ERROR_PREFIX = "Response: "; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds @@ -620,13 +618,15 @@ class SipSessionGroup implements SipListener { Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); return true; } else if (evt instanceof TransactionTerminatedEvent) { - if (evt instanceof TimeoutEvent) { - processTimeout((TimeoutEvent) evt); - } else { - processTransactionTerminated( - (TransactionTerminatedEvent) evt); + if (isCurrentTransaction((TransactionTerminatedEvent) evt)) { + if (evt instanceof TimeoutEvent) { + processTimeout((TimeoutEvent) evt); + } else { + processTransactionTerminated( + (TransactionTerminatedEvent) evt); + } + return true; } - return true; } else if (isRequestEvent(Request.OPTIONS, evt)) { mSipHelper.sendResponse((RequestEvent) evt, Response.OK); return true; @@ -646,6 +646,37 @@ class SipSessionGroup implements SipListener { } } + private boolean isCurrentTransaction(TransactionTerminatedEvent event) { + Transaction current = event.isServerTransaction() + ? mServerTransaction + : mClientTransaction; + Transaction target = event.isServerTransaction() + ? event.getServerTransaction() + : event.getClientTransaction(); + + if ((current != target) && (mState != SipSession.State.PINGING)) { + Log.d(TAG, "not the current transaction; current=" + + toString(current) + ", target=" + toString(target)); + return false; + } else if (current != null) { + Log.d(TAG, "transaction terminated: " + toString(current)); + return true; + } else { + // no transaction; shouldn't be here; ignored + return true; + } + } + + private String toString(Transaction transaction) { + if (transaction == null) return "null"; + Request request = transaction.getRequest(); + Dialog dialog = transaction.getDialog(); + CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME); + return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(), + cseq.getSeqNumber(), transaction.getState(), + ((dialog == null) ? "-" : dialog.getState())); + } + private void processTransactionTerminated( TransactionTerminatedEvent event) { switch (mState) { @@ -661,19 +692,7 @@ class SipSessionGroup implements SipListener { } private void processTimeout(TimeoutEvent event) { - Log.d(TAG, "processing Timeout..." + event); - Transaction current = event.isServerTransaction() - ? mServerTransaction - : mClientTransaction; - Transaction target = event.isServerTransaction() - ? event.getServerTransaction() - : event.getClientTransaction(); - - if ((current != target) && (mState != SipSession.State.PINGING)) { - Log.d(TAG, "not the current transaction; current=" + current - + ", timed out=" + target); - return; - } + Log.d(TAG, "processing Timeout..."); switch (mState) { case SipSession.State.REGISTERING: case SipSession.State.DEREGISTERING: @@ -810,6 +829,12 @@ class SipSessionGroup implements SipListener { } } + private boolean crossDomainAuthenticationRequired(Response response) { + String realm = getRealmFromResponse(response); + if (realm == null) realm = ""; + return !mLocalProfile.getSipDomain().trim().equals(realm.trim()); + } + private AccountManager getAccountManager() { return new AccountManager() { public UserCredentials getCredentials(ClientTransaction @@ -831,6 +856,15 @@ class SipSessionGroup implements SipListener { }; } + private String getRealmFromResponse(Response response) { + WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( + SIPHeaderNames.WWW_AUTHENTICATE); + if (wwwAuth != null) return wwwAuth.getRealm(); + ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( + SIPHeaderNames.PROXY_AUTHENTICATE); + return (proxyAuth == null) ? null : proxyAuth.getRealm(); + } + private String getNonceFromResponse(Response response) { WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( SIPHeaderNames.WWW_AUTHENTICATE); @@ -924,6 +958,11 @@ class SipSessionGroup implements SipListener { int statusCode = response.getStatusCode(); switch (statusCode) { case Response.RINGING: + case Response.CALL_IS_BEING_FORWARDED: + case Response.QUEUED: + case Response.SESSION_PROGRESS: + // feedback any provisional responses (except TRYING) as + // ring back for better UX if (mState == SipSession.State.OUTGOING_CALL) { mState = SipSession.State.OUTGOING_CALL_RING_BACK; mProxy.onRingingBack(this); @@ -937,7 +976,10 @@ class SipSessionGroup implements SipListener { return true; case Response.UNAUTHORIZED: case Response.PROXY_AUTHENTICATION_REQUIRED: - if (handleAuthentication(event)) { + if (crossDomainAuthenticationRequired(response)) { + onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, + getRealmFromResponse(response)); + } else if (handleAuthentication(event)) { addSipSession(this); } else if (mLastNonce == null) { onError(SipErrorCode.SERVER_ERROR, @@ -982,19 +1024,19 @@ class SipSessionGroup implements SipListener { Response response = event.getResponse(); int statusCode = response.getStatusCode(); if (expectResponse(Request.CANCEL, evt)) { + if (statusCode == Response.OK) { + // do nothing; wait for REQUEST_TERMINATED + return true; + } + } else if (expectResponse(Request.INVITE, evt)) { switch (statusCode) { case Response.OK: - // do nothing; wait for REQUEST_TERMINATED + outgoingCall(evt); // abort Cancel return true; case Response.REQUEST_TERMINATED: endCallNormally(); return true; } - } else if (expectResponse(Request.INVITE, evt)) { - if (statusCode == Response.OK) { - outgoingCall(evt); // abort Cancel - return true; - } } else { return false; } @@ -1060,8 +1102,8 @@ class SipSessionGroup implements SipListener { } private String createErrorMessage(Response response) { - return String.format(SERVER_ERROR_PREFIX + "%s (%d)", - response.getReasonPhrase(), response.getStatusCode()); + return String.format("%s (%d)", response.getReasonPhrase(), + response.getStatusCode()); } private void establishCall() { @@ -1165,8 +1207,6 @@ class SipSessionGroup implements SipListener { return SipErrorCode.INVALID_REMOTE_URI; } else if (exception instanceof IOException) { return SipErrorCode.SOCKET_ERROR; - } else if (message.startsWith(SERVER_ERROR_PREFIX)) { - return SipErrorCode.SERVER_ERROR; } else { return SipErrorCode.CLIENT_ERROR; } diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/voip/java/com/android/server/sip/SipSessionListenerProxy.java index f8be0a8..f8be0a8 100644 --- a/services/java/com/android/server/sip/SipSessionListenerProxy.java +++ b/voip/java/com/android/server/sip/SipSessionListenerProxy.java diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk index a364355..29683bd 100644 --- a/voip/jni/rtp/Android.mk +++ b/voip/jni/rtp/Android.mk @@ -26,16 +26,21 @@ LOCAL_SRC_FILES := \ util.cpp \ rtp_jni.cpp +LOCAL_SRC_FILES += \ + G711Codec.cpp \ + GsmCodec.cpp + LOCAL_SHARED_LIBRARIES := \ libnativehelper \ libcutils \ libutils \ libmedia -LOCAL_STATIC_LIBRARIES := +LOCAL_STATIC_LIBRARIES := libgsm LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) + $(JNI_H_INCLUDE) \ + external/libgsm/inc LOCAL_CFLAGS += -fvisibility=hidden diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp index 4d8d36c..fc33ef2 100644 --- a/voip/jni/rtp/AudioCodec.cpp +++ b/voip/jni/rtp/AudioCodec.cpp @@ -18,124 +18,9 @@ #include "AudioCodec.h" -namespace { - -int8_t gExponents[128] = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, -}; - -//------------------------------------------------------------------------------ - -class UlawCodec : public AudioCodec -{ -public: - int set(int sampleRate, const char *fmtp) { - mSampleCount = sampleRate / 50; - return mSampleCount; - } - int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); -private: - int mSampleCount; -}; - -int UlawCodec::encode(void *payload, int16_t *samples) -{ - int8_t *ulaws = (int8_t *)payload; - for (int i = 0; i < mSampleCount; ++i) { - int sample = samples[i]; - int sign = (sample >> 8) & 0x80; - if (sample < 0) { - sample = -sample; - } - sample += 132; - if (sample > 32767) { - sample = 32767; - } - int exponent = gExponents[sample >> 8]; - int mantissa = (sample >> (exponent + 3)) & 0x0F; - ulaws[i] = ~(sign | (exponent << 4) | mantissa); - } - return mSampleCount; -} - -int UlawCodec::decode(int16_t *samples, void *payload, int length) -{ - int8_t *ulaws = (int8_t *)payload; - for (int i = 0; i < length; ++i) { - int ulaw = ~ulaws[i]; - int exponent = (ulaw >> 4) & 0x07; - int mantissa = ulaw & 0x0F; - int sample = (((mantissa << 3) + 132) << exponent) - 132; - samples[i] = (ulaw < 0 ? -sample : sample); - } - return length; -} - -AudioCodec *newUlawCodec() -{ - return new UlawCodec; -} - -//------------------------------------------------------------------------------ - -class AlawCodec : public AudioCodec -{ -public: - int set(int sampleRate, const char *fmtp) { - mSampleCount = sampleRate / 50; - return mSampleCount; - } - int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); -private: - int mSampleCount; -}; - -int AlawCodec::encode(void *payload, int16_t *samples) -{ - int8_t *alaws = (int8_t *)payload; - for (int i = 0; i < mSampleCount; ++i) { - int sample = samples[i]; - int sign = (sample >> 8) & 0x80; - if (sample < 0) { - sample = -sample; - } - if (sample > 32767) { - sample = 32767; - } - int exponent = gExponents[sample >> 8]; - int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F; - alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5; - } - return mSampleCount; -} - -int AlawCodec::decode(int16_t *samples, void *payload, int length) -{ - int8_t *alaws = (int8_t *)payload; - for (int i = 0; i < length; ++i) { - int alaw = alaws[i] ^ 0x55; - int exponent = (alaw >> 4) & 0x07; - int mantissa = alaw & 0x0F; - int sample = (exponent == 0 ? (mantissa << 4) + 8 : - ((mantissa << 3) + 132) << exponent); - samples[i] = (alaw < 0 ? sample : -sample); - } - return length; -} - -AudioCodec *newAlawCodec() -{ - return new AlawCodec; -} +extern AudioCodec *newAlawCodec(); +extern AudioCodec *newUlawCodec(); +extern AudioCodec *newGsmCodec(); struct AudioCodecType { const char *name; @@ -143,11 +28,10 @@ struct AudioCodecType { } gAudioCodecTypes[] = { {"PCMA", newAlawCodec}, {"PCMU", newUlawCodec}, + {"GSM", newGsmCodec}, {NULL, NULL}, }; -} // namespace - AudioCodec *newAudioCodec(const char *codecName) { AudioCodecType *type = gAudioCodecTypes; diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp index 7cf0613..72c882b 100644 --- a/voip/jni/rtp/AudioGroup.cpp +++ b/voip/jni/rtp/AudioGroup.cpp @@ -86,6 +86,8 @@ public: void decode(int tick); private: + bool isNatAddress(struct sockaddr_storage *addr); + enum { NORMAL = 0, SEND_ONLY = 1, @@ -316,6 +318,16 @@ void AudioStream::encode(int tick, AudioStream *chain) sizeof(mRemote)); } +bool AudioStream::isNatAddress(struct sockaddr_storage *addr) { + if (addr->ss_family != AF_INET) return false; + struct sockaddr_in *s4 = (struct sockaddr_in *)addr; + unsigned char *d = (unsigned char *) &s4->sin_addr; + if ((d[0] == 10) + || ((d[0] == 172) && (d[1] & 0x10)) + || ((d[0] == 192) && (d[1] == 168))) return true; + return false; +} + void AudioStream::decode(int tick) { char c; @@ -363,8 +375,21 @@ void AudioStream::decode(int tick) MSG_TRUNC | MSG_DONTWAIT) >> 1; } else { __attribute__((aligned(4))) uint8_t buffer[2048]; - length = recv(mSocket, buffer, sizeof(buffer), - MSG_TRUNC | MSG_DONTWAIT); + struct sockaddr_storage src_addr; + socklen_t addrlen; + length = recvfrom(mSocket, buffer, sizeof(buffer), + MSG_TRUNC|MSG_DONTWAIT, (sockaddr*)&src_addr, &addrlen); + + // The following if clause is for fixing the target address if + // proxy server did not replace the NAT address with its media + // port in SDP. Although it is proxy server's responsibility for + // replacing the connection address with correct one, we will change + // the target address as we detect the difference for now until we + // know the best way to get rid of this issue. + if ((memcmp((void*)&src_addr, (void*)&mRemote, addrlen) != 0) && + isNatAddress(&mRemote)) { + memcpy((void*)&mRemote, (void*)&src_addr, addrlen); + } // Do we need to check SSRC, sequence, and timestamp? They are not // reliable but at least they can be used to identify duplicates? @@ -436,18 +461,15 @@ private: EC_ENABLED = 3, LAST_MODE = 3, }; - int mMode; + AudioStream *mChain; int mEventQueue; volatile int mDtmfEvent; + int mMode; + int mSampleRate; int mSampleCount; int mDeviceSocket; - AudioTrack mTrack; - AudioRecord mRecord; - - bool networkLoop(); - bool deviceLoop(); class NetworkThread : public Thread { @@ -465,10 +487,7 @@ private: private: AudioGroup *mGroup; - bool threadLoop() - { - return mGroup->networkLoop(); - } + bool threadLoop(); }; sp<NetworkThread> mNetworkThread; @@ -479,9 +498,6 @@ private: bool start() { - char c; - while (recv(mGroup->mDeviceSocket, &c, 1, MSG_DONTWAIT) == 1); - if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { LOGE("cannot start device thread"); return false; @@ -491,10 +507,7 @@ private: private: AudioGroup *mGroup; - bool threadLoop() - { - return mGroup->deviceLoop(); - } + bool threadLoop(); }; sp<DeviceThread> mDeviceThread; }; @@ -514,8 +527,6 @@ AudioGroup::~AudioGroup() { mNetworkThread->requestExitAndWait(); mDeviceThread->requestExitAndWait(); - mTrack.stop(); - mRecord.stop(); close(mEventQueue); close(mDeviceSocket); while (mChain) { @@ -534,40 +545,9 @@ bool AudioGroup::set(int sampleRate, int sampleCount) return false; } + mSampleRate = sampleRate; mSampleCount = sampleCount; - // Find out the frame count for AudioTrack and AudioRecord. - int output = 0; - int input = 0; - if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, - sampleRate) != NO_ERROR || output <= 0 || - AudioRecord::getMinFrameCount(&input, sampleRate, - AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { - LOGE("cannot compute frame count"); - return false; - } - LOGD("reported frame count: output %d, input %d", output, input); - - if (output < sampleCount * 2) { - output = sampleCount * 2; - } - if (input < sampleCount * 2) { - input = sampleCount * 2; - } - LOGD("adjusted frame count: output %d, input %d", output, input); - - // Initialize AudioTrack and AudioRecord. - if (mTrack.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || - mRecord.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { - LOGE("cannot initialize audio device"); - return false; - } - LOGD("latency: output %d, input %d", mTrack.latency(), mRecord.latency()); - - // TODO: initialize echo canceler here. - // Create device socket. int pair[2]; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) { @@ -585,13 +565,11 @@ bool AudioGroup::set(int sampleRate, int sampleCount) return false; } - // Give device socket a reasonable timeout and buffer size. + // Give device socket a reasonable timeout. timeval tv; tv.tv_sec = 0; tv.tv_usec = 1000 * sampleCount / sampleRate * 500; - if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) || - setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) || - setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) { + if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) { LOGE("setsockopt: %s", strerror(errno)); return false; } @@ -619,29 +597,10 @@ bool AudioGroup::setMode(int mode) return true; } + mDeviceThread->requestExitAndWait(); LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); mMode = mode; - - mDeviceThread->requestExitAndWait(); - if (mode == ON_HOLD) { - mTrack.stop(); - mRecord.stop(); - return true; - } - - mTrack.start(); - if (mode == MUTED) { - mRecord.stop(); - } else { - mRecord.start(); - } - - if (!mDeviceThread->start()) { - mTrack.stop(); - mRecord.stop(); - return false; - } - return true; + return (mode == ON_HOLD) || mDeviceThread->start(); } bool AudioGroup::sendDtmf(int event) @@ -716,15 +675,16 @@ bool AudioGroup::remove(int socket) return true; } -bool AudioGroup::networkLoop() +bool AudioGroup::NetworkThread::threadLoop() { + AudioStream *chain = mGroup->mChain; int tick = elapsedRealtime(); int deadline = tick + 10; int count = 0; - for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + for (AudioStream *stream = chain; stream; stream = stream->mNext) { if (!stream->mTick || tick - stream->mTick >= 0) { - stream->encode(tick, mChain); + stream->encode(tick, chain); } if (deadline - stream->mTick > 0) { deadline = stream->mTick; @@ -732,12 +692,12 @@ bool AudioGroup::networkLoop() ++count; } - if (mDtmfEvent != -1) { - int event = mDtmfEvent; - for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + int event = mGroup->mDtmfEvent; + if (event != -1) { + for (AudioStream *stream = chain; stream; stream = stream->mNext) { stream->sendDtmf(event); } - mDtmfEvent = -1; + mGroup->mDtmfEvent = -1; } deadline -= tick; @@ -746,7 +706,7 @@ bool AudioGroup::networkLoop() } epoll_event events[count]; - count = epoll_wait(mEventQueue, events, count, deadline); + count = epoll_wait(mGroup->mEventQueue, events, count, deadline); if (count == -1) { LOGE("epoll_wait: %s", strerror(errno)); return false; @@ -758,70 +718,125 @@ bool AudioGroup::networkLoop() return true; } -bool AudioGroup::deviceLoop() +bool AudioGroup::DeviceThread::threadLoop() { - int16_t output[mSampleCount]; + int mode = mGroup->mMode; + int sampleRate = mGroup->mSampleRate; + int sampleCount = mGroup->mSampleCount; + int deviceSocket = mGroup->mDeviceSocket; + + // Find out the frame count for AudioTrack and AudioRecord. + int output = 0; + int input = 0; + if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, + sampleRate) != NO_ERROR || output <= 0 || + AudioRecord::getMinFrameCount(&input, sampleRate, + AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + LOGE("cannot compute frame count"); + return false; + } + LOGD("reported frame count: output %d, input %d", output, input); + + if (output < sampleCount * 2) { + output = sampleCount * 2; + } + if (input < sampleCount * 2) { + input = sampleCount * 2; + } + LOGD("adjusted frame count: output %d, input %d", output, input); - if (recv(mDeviceSocket, output, sizeof(output), 0) <= 0) { - memset(output, 0, sizeof(output)); + // Initialize AudioTrack and AudioRecord. + AudioTrack track; + AudioRecord record; + if (track.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || + record.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { + LOGE("cannot initialize audio device"); + return false; } + LOGD("latency: output %d, input %d", track.latency(), record.latency()); - int16_t input[mSampleCount]; - int toWrite = mSampleCount; - int toRead = (mMode == MUTED) ? 0 : mSampleCount; - int chances = 100; + // TODO: initialize echo canceler here. - while (--chances > 0 && (toWrite > 0 || toRead > 0)) { - if (toWrite > 0) { - AudioTrack::Buffer buffer; - buffer.frameCount = toWrite; + // Give device socket a reasonable buffer size. + setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)); + setsockopt(deviceSocket, SOL_SOCKET, SO_SNDBUF, &output, sizeof(output)); - status_t status = mTrack.obtainBuffer(&buffer, 1); - if (status == NO_ERROR) { - memcpy(buffer.i8, &output[mSampleCount - toWrite], buffer.size); - toWrite -= buffer.frameCount; - mTrack.releaseBuffer(&buffer); - } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot write to AudioTrack"); - return false; - } + // Drain device socket. + char c; + while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); + + // Start your engine! + track.start(); + if (mode != MUTED) { + record.start(); + } + + while (!exitPending()) { + int16_t output[sampleCount]; + if (recv(deviceSocket, output, sizeof(output), 0) <= 0) { + memset(output, 0, sizeof(output)); } - if (toRead > 0) { - AudioRecord::Buffer buffer; - buffer.frameCount = mRecord.frameCount(); - - status_t status = mRecord.obtainBuffer(&buffer, 1); - if (status == NO_ERROR) { - int count = ((int)buffer.frameCount < toRead) ? - buffer.frameCount : toRead; - memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2); - toRead -= count; - if (buffer.frameCount < mRecord.frameCount()) { - buffer.frameCount = count; + int16_t input[sampleCount]; + int toWrite = sampleCount; + int toRead = (mode == MUTED) ? 0 : sampleCount; + int chances = 100; + + while (--chances > 0 && (toWrite > 0 || toRead > 0)) { + if (toWrite > 0) { + AudioTrack::Buffer buffer; + buffer.frameCount = toWrite; + + status_t status = track.obtainBuffer(&buffer, 1); + if (status == NO_ERROR) { + int offset = sampleCount - toWrite; + memcpy(buffer.i8, &output[offset], buffer.size); + toWrite -= buffer.frameCount; + track.releaseBuffer(&buffer); + } else if (status != TIMED_OUT && status != WOULD_BLOCK) { + LOGE("cannot write to AudioTrack"); + break; + } + } + + if (toRead > 0) { + AudioRecord::Buffer buffer; + buffer.frameCount = record.frameCount(); + + status_t status = record.obtainBuffer(&buffer, 1); + if (status == NO_ERROR) { + int count = ((int)buffer.frameCount < toRead) ? + buffer.frameCount : toRead; + memcpy(&input[sampleCount - toRead], buffer.i8, count * 2); + toRead -= count; + if (buffer.frameCount < record.frameCount()) { + buffer.frameCount = count; + } + record.releaseBuffer(&buffer); + } else if (status != TIMED_OUT && status != WOULD_BLOCK) { + LOGE("cannot read from AudioRecord"); + break; } - mRecord.releaseBuffer(&buffer); - } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot read from AudioRecord"); - return false; } } - } - if (!chances) { - LOGE("device loop timeout"); - return false; - } + if (chances <= 0) { + LOGE("device loop timeout"); + break; + } - if (mMode != MUTED) { - if (mMode == NORMAL) { - send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); - } else { - // TODO: Echo canceller runs here. - send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); + if (mode != MUTED) { + if (mode == NORMAL) { + send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); + } else { + // TODO: Echo canceller runs here. + send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); + } } } - return true; + return false; } //------------------------------------------------------------------------------ diff --git a/voip/jni/rtp/G711Codec.cpp b/voip/jni/rtp/G711Codec.cpp new file mode 100644 index 0000000..091afa9 --- /dev/null +++ b/voip/jni/rtp/G711Codec.cpp @@ -0,0 +1,138 @@ +/* + * Copyrightm (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. + */ + +#include "AudioCodec.h" + +namespace { + +int8_t gExponents[128] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +}; + +//------------------------------------------------------------------------------ + +class UlawCodec : public AudioCodec +{ +public: + int set(int sampleRate, const char *fmtp) { + mSampleCount = sampleRate / 50; + return mSampleCount; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int UlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + sample += 132; + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent + 3)) & 0x0F; + ulaws[i] = ~(sign | (exponent << 4) | mantissa); + } + return mSampleCount; +} + +int UlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int ulaw = ~ulaws[i]; + int exponent = (ulaw >> 4) & 0x07; + int mantissa = ulaw & 0x0F; + int sample = (((mantissa << 3) + 132) << exponent) - 132; + samples[i] = (ulaw < 0 ? -sample : sample); + } + return length; +} + +//------------------------------------------------------------------------------ + +class AlawCodec : public AudioCodec +{ +public: + int set(int sampleRate, const char *fmtp) { + mSampleCount = sampleRate / 50; + return mSampleCount; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int AlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F; + alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5; + } + return mSampleCount; +} + +int AlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int alaw = alaws[i] ^ 0x55; + int exponent = (alaw >> 4) & 0x07; + int mantissa = alaw & 0x0F; + int sample = (exponent == 0 ? (mantissa << 4) + 8 : + ((mantissa << 3) + 132) << exponent); + samples[i] = (alaw < 0 ? sample : -sample); + } + return length; +} + +} // namespace + +AudioCodec *newUlawCodec() +{ + return new UlawCodec; +} + +AudioCodec *newAlawCodec() +{ + return new AlawCodec; +} diff --git a/voip/jni/rtp/GsmCodec.cpp b/voip/jni/rtp/GsmCodec.cpp new file mode 100644 index 0000000..8d2286e --- /dev/null +++ b/voip/jni/rtp/GsmCodec.cpp @@ -0,0 +1,74 @@ +/* + * Copyrightm (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. + */ + +#include "AudioCodec.h" + +extern "C" { +#include "gsm.h" +} + +namespace { + +class GsmCodec : public AudioCodec +{ +public: + GsmCodec() { + mEncode = gsm_create(); + mDecode = gsm_create(); + } + + ~GsmCodec() { + if (mEncode) { + gsm_destroy(mEncode); + } + if (mDecode) { + gsm_destroy(mDecode); + } + } + + int set(int sampleRate, const char *fmtp) { + return (sampleRate == 8000 && mEncode && mDecode) ? 160 : -1; + } + + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); + +private: + gsm mEncode; + gsm mDecode; +}; + +int GsmCodec::encode(void *payload, int16_t *samples) +{ + gsm_encode(mEncode, samples, (unsigned char *)payload); + return 33; +} + +int GsmCodec::decode(int16_t *samples, void *payload, int length) +{ + if (length == 33 && + gsm_decode(mDecode, (unsigned char *)payload, samples) == 0) { + return 160; + } + return -1; +} + +} // namespace + +AudioCodec *newGsmCodec() +{ + return new GsmCodec; +} diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 7e26028..e82c003 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -63,9 +63,11 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.app.backup.IBackupManager; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothProfile; import android.content.ContentResolver; import android.content.Intent; import android.content.Context; @@ -444,8 +446,14 @@ public class WifiStateMachine extends HierarchicalStateMachine { mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0"); mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler()); - mBluetoothHeadset = new BluetoothHeadset(mContext, null); mLinkProperties = new LinkProperties(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.A2DP); + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEADSET); + } mNetworkInfo.setIsAvailable(false); mLinkProperties.clear(); @@ -1278,24 +1286,50 @@ public class WifiStateMachine extends HierarchicalStateMachine { * * @return Whether to disable coexistence mode. */ - private boolean shouldDisableCoexistenceMode() { - int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset()); - return state == BluetoothHeadset.STATE_DISCONNECTED; + private synchronized boolean shouldDisableCoexistenceMode() { + Set<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices(); + + return (devices.size() != 0 ? true : false); } - private void checkIsBluetoothPlaying() { + private synchronized void checkIsBluetoothPlaying() { boolean isBluetoothPlaying = false; - Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks(); + if (mBluetoothA2dp != null) { + Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedDevices(); - for (BluetoothDevice device : connected) { - if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) { - isBluetoothPlaying = true; - break; + for (BluetoothDevice device : connected) { + if (mBluetoothA2dp.isA2dpPlaying(device)) { + isBluetoothPlaying = true; + break; + } } } setBluetoothScanMode(isBluetoothPlaying); } + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + synchronized (WifiStateMachine.this) { + if (profile == BluetoothProfile.HEADSET) { + mBluetoothHeadset = (BluetoothHeadset) proxy; + } else if (profile == BluetoothProfile.A2DP) { + mBluetoothA2dp = (BluetoothA2dp)proxy; + } + } + } + + public void onServiceDisconnected(int profile) { + synchronized (WifiStateMachine.this) { + if (profile == BluetoothProfile.HEADSET) { + mBluetoothHeadset = null; + } else if (profile == BluetoothProfile.A2DP) { + mBluetoothA2dp = null; + } + } + } + }; + private void sendScanResultsAvailableBroadcast() { if (!ActivityManagerNative.isSystemReady()) return; @@ -1905,9 +1939,6 @@ public class WifiStateMachine extends HierarchicalStateMachine { //TODO: initialize and fix multicast filtering //mWM.initializeMulticastFiltering(); - if (mBluetoothA2dp == null) { - mBluetoothA2dp = new BluetoothA2dp(mContext); - } checkIsBluetoothPlaying(); sendSupplicantConnectionChangedBroadcast(true); @@ -2458,7 +2489,10 @@ public class WifiStateMachine extends HierarchicalStateMachine { mModifiedBluetoothCoexistenceMode = false; mPowerMode = DRIVER_POWER_MODE_AUTO; - if (shouldDisableCoexistenceMode()) { + // TODO(): Incorporate the else part in the state machine + // If mBluetoothHeadset == null, means it not conencted to the + // service yet. + if (mBluetoothHeadset != null && shouldDisableCoexistenceMode()) { /* * There are problems setting the Wi-Fi driver's power * mode to active when bluetooth coexistence mode is |