Merge "Add --auto-add-overlay option to aapt."
diff --git a/Android.mk b/Android.mk
index 682f286..ec6f96b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -111,8 +111,8 @@
core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/os/ICheckinService.aidl \
core/java/android/os/IMessenger.aidl \
- core/java/android/os/IMountService.aidl \
- core/java/android/os/IMountServiceListener.aidl \
+ core/java/android/os/storage/IMountService.aidl \
+ core/java/android/os/storage/IMountServiceListener.aidl \
core/java/android/os/INetworkManagementService.aidl \
core/java/android/os/INetStatService.aidl \
core/java/android/os/IParentalControlCallback.aidl \
@@ -222,6 +222,7 @@
frameworks/base/core/java/android/content/ComponentName.aidl \
frameworks/base/core/java/android/content/Intent.aidl \
frameworks/base/core/java/android/content/IntentSender.aidl \
+ frameworks/base/core/java/android/content/PeriodicSync.aidl \
frameworks/base/core/java/android/content/SyncStats.aidl \
frameworks/base/core/java/android/content/res/Configuration.aidl \
frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \
diff --git a/api/current.xml b/api/current.xml
index 5e34322..b155c45 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1035,6 +1035,17 @@
visibility="public"
>
</field>
+<field name="SET_TIME"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.SET_TIME""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SET_TIME_ZONE"
type="java.lang.String"
transient="false"
@@ -18808,6 +18819,19 @@
<parameter name="operation" type="android.app.PendingIntent">
</parameter>
</method>
+<method name="setTime"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="millis" type="long">
+</parameter>
+</method>
<method name="setTimeZone"
return="void"
abstract="false"
@@ -24873,17 +24897,6 @@
visibility="public"
>
</field>
-<field name="INTENT_ACTION_SELECT_SEARCH_SOURCE"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""android.intent.action.SELECT_SEARCH_SOURCE""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="INTENT_ACTION_WEB_SEARCH_SETTINGS"
type="java.lang.String"
transient="false"
@@ -29811,6 +29824,8 @@
deprecated="not deprecated"
visibility="public"
>
+<implements name="java.lang.Cloneable">
+</implements>
<implements name="java.lang.Comparable">
</implements>
<implements name="android.os.Parcelable">
@@ -29861,6 +29876,17 @@
<parameter name="in" type="android.os.Parcel">
</parameter>
</constructor>
+<method name="clone"
+ return="android.content.ComponentName"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="compareTo"
return="int"
abstract="false"
@@ -31094,6 +31120,25 @@
<parameter name="name" type="java.lang.String">
</parameter>
</method>
+<method name="addPeriodicSync"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+<parameter name="pollFrequency" type="long">
+</parameter>
+</method>
<method name="addStatusChangeListener"
return="java.lang.Object"
abstract="false"
@@ -31214,6 +31259,21 @@
visibility="public"
>
</method>
+<method name="getPeriodicSyncs"
+ return="java.util.List<android.content.PeriodicSync>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+</method>
<method name="getSyncAdapterTypes"
return="android.content.SyncAdapterType[]"
abstract="false"
@@ -31449,6 +31509,23 @@
<parameter name="observer" type="android.database.ContentObserver">
</parameter>
</method>
+<method name="removePeriodicSync"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+</method>
<method name="removeStatusChangeListener"
return="void"
abstract="false"
@@ -31663,6 +31740,17 @@
visibility="public"
>
</field>
+<field name="SYNC_EXTRAS_DO_NOT_RETRY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""do_not_retry""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SYNC_EXTRAS_EXPEDITED"
type="java.lang.String"
transient="false"
@@ -31685,6 +31773,28 @@
visibility="public"
>
</field>
+<field name="SYNC_EXTRAS_IGNORE_BACKOFF"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""ignore_backoff""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SYNC_EXTRAS_IGNORE_SETTINGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""ignore_settings""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SYNC_EXTRAS_INITIALIZE"
type="java.lang.String"
transient="false"
@@ -39732,6 +39842,109 @@
>
</method>
</class>
+<class name="PeriodicSync"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="PeriodicSync"
+ type="android.content.PeriodicSync"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+<parameter name="period" type="long">
+</parameter>
+</constructor>
+<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="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="account"
+ type="android.accounts.Account"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="authority"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="extras"
+ type="android.os.Bundle"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="period"
+ type="long"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="ReceiverCallNotAllowedException"
extends="android.util.AndroidRuntimeException"
abstract="false"
@@ -41683,17 +41896,6 @@
visibility="public"
>
</field>
-<field name="resourceDirs"
- type="java.lang.String[]"
- transient="false"
- volatile="false"
- value="null"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="sharedLibraryFiles"
type="java.lang.String[]"
transient="false"
@@ -52903,6 +53105,72 @@
<parameter name="sleepAfterYieldDelay" type="long">
</parameter>
</method>
+<field name="CONFLICT_ABORT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONFLICT_FAIL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONFLICT_IGNORE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONFLICT_NONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONFLICT_REPLACE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CONFLICT_ROLLBACK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="CREATE_IF_NECESSARY"
type="int"
transient="false"
@@ -52959,81 +53227,6 @@
>
</field>
</class>
-<class name="SQLiteDatabase.ConflictAlgorithm"
- extends="java.lang.Object"
- abstract="false"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-<field name="ABORT"
- type="int"
- transient="false"
- volatile="false"
- value="2"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="FAIL"
- type="int"
- transient="false"
- volatile="false"
- value="3"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="IGNORE"
- type="int"
- transient="false"
- volatile="false"
- value="4"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="NONE"
- type="int"
- transient="false"
- volatile="false"
- value="0"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="REPLACE"
- type="int"
- transient="false"
- volatile="false"
- value="5"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="ROLLBACK"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-</class>
<interface name="SQLiteDatabase.CursorFactory"
abstract="true"
static="true"
@@ -55617,7 +55810,7 @@
>
</field>
</class>
-<class name="GestureUtilities"
+<class name="GestureUtils"
extends="java.lang.Object"
abstract="false"
static="false"
@@ -77410,32 +77603,6 @@
<parameter name="enabledOnly" type="boolean">
</parameter>
</method>
-<method name="installGeocodeProvider"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="provider" type="android.location.LocationManager.GeocodeProvider">
-</parameter>
-</method>
-<method name="installLocationProvider"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="provider" type="android.location.LocationProviderImpl">
-</parameter>
-</method>
<method name="isProviderEnabled"
return="boolean"
abstract="false"
@@ -77527,19 +77694,6 @@
<parameter name="intent" type="android.app.PendingIntent">
</parameter>
</method>
-<method name="reportLocation"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="location" type="android.location.Location">
-</parameter>
-</method>
<method name="requestLocationUpdates"
return="void"
abstract="false"
@@ -77732,62 +77886,6 @@
>
</field>
</class>
-<interface name="LocationManager.GeocodeProvider"
- abstract="true"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="getFromLocation"
- return="java.lang.String"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="latitude" type="double">
-</parameter>
-<parameter name="longitude" type="double">
-</parameter>
-<parameter name="maxResults" type="int">
-</parameter>
-<parameter name="params" type="android.location.GeocoderParams">
-</parameter>
-<parameter name="addrs" type="java.util.List<android.location.Address>">
-</parameter>
-</method>
-<method name="getFromLocationName"
- return="java.lang.String"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="locationName" type="java.lang.String">
-</parameter>
-<parameter name="lowerLeftLatitude" type="double">
-</parameter>
-<parameter name="lowerLeftLongitude" type="double">
-</parameter>
-<parameter name="upperRightLatitude" type="double">
-</parameter>
-<parameter name="upperRightLongitude" type="double">
-</parameter>
-<parameter name="maxResults" type="int">
-</parameter>
-<parameter name="params" type="android.location.GeocoderParams">
-</parameter>
-<parameter name="addrs" type="java.util.List<android.location.Address>">
-</parameter>
-</method>
-</interface>
<class name="LocationProvider"
extends="java.lang.Object"
abstract="true"
@@ -77953,166 +78051,6 @@
>
</field>
</class>
-<class name="LocationProviderImpl"
- extends="android.location.LocationProvider"
- abstract="true"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<constructor name="LocationProviderImpl"
- type="android.location.LocationProviderImpl"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="name" type="java.lang.String">
-</parameter>
-</constructor>
-<method name="addListener"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="uid" type="int">
-</parameter>
-</method>
-<method name="disable"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="enable"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="enableLocationTracking"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="enable" type="boolean">
-</parameter>
-</method>
-<method name="getStatus"
- return="int"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="extras" type="android.os.Bundle">
-</parameter>
-</method>
-<method name="getStatusUpdateTime"
- return="long"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="removeListener"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="uid" type="int">
-</parameter>
-</method>
-<method name="sendExtraCommand"
- return="boolean"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="command" type="java.lang.String">
-</parameter>
-<parameter name="extras" type="android.os.Bundle">
-</parameter>
-</method>
-<method name="setMinTime"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="minTime" type="long">
-</parameter>
-</method>
-<method name="updateLocation"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="location" type="android.location.Location">
-</parameter>
-</method>
-<method name="updateNetworkState"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="state" type="int">
-</parameter>
-<parameter name="info" type="android.net.NetworkInfo">
-</parameter>
-</method>
-</class>
</package>
<package name="android.media"
>
@@ -80725,6 +80663,21 @@
<parameter name="tag" type="java.lang.String">
</parameter>
</method>
+<method name="getAttributeDouble"
+ return="double"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="defaultValue" type="double">
+</parameter>
+</method>
<method name="getAttributeInt"
return="int"
abstract="false"
@@ -80924,6 +80877,17 @@
visibility="public"
>
</field>
+<field name="TAG_FOCAL_LENGTH"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""FocalLength""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TAG_GPS_DATESTAMP"
type="java.lang.String"
transient="false"
@@ -86759,7 +86723,7 @@
visibility="public"
>
</method>
-<method name="getMobileRxPkts"
+<method name="getMobileRxPackets"
return="long"
abstract="false"
native="false"
@@ -86781,7 +86745,7 @@
visibility="public"
>
</method>
-<method name="getMobileTxPkts"
+<method name="getMobileTxPackets"
return="long"
abstract="false"
native="false"
@@ -86803,7 +86767,7 @@
visibility="public"
>
</method>
-<method name="getTotalRxPkts"
+<method name="getTotalRxPackets"
return="long"
abstract="false"
native="false"
@@ -86825,7 +86789,7 @@
visibility="public"
>
</method>
-<method name="getTotalTxPkts"
+<method name="getTotalTxPackets"
return="long"
abstract="false"
native="false"
@@ -117682,6 +117646,218 @@
</method>
</class>
</package>
+<package name="android.os.storage"
+>
+<class name="StorageEventListener"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageEventListener"
+ type="android.os.storage.StorageEventListener"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onStorageStateChanged"
+ 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="oldState" type="java.lang.String">
+</parameter>
+<parameter name="newState" type="java.lang.String">
+</parameter>
+</method>
+<method name="onUsbMassStorageConnectionChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="connected" type="boolean">
+</parameter>
+</method>
+</class>
+<class name="StorageManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="disableUsbMassStorage"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="enableUsbMassStorage"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageConnected"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="registerListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+<method name="unregisterListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+</class>
+<class name="StorageResultCode"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageResultCode"
+ type="android.os.storage.StorageResultCode"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<field name="OperationFailedInternalError"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaBlank"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaCorrupt"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedNoMedia"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedVolumeNotMounted"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationSucceeded"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+</package>
<package name="android.preference"
>
<class name="CheckBoxPreference"
@@ -135027,7 +135203,7 @@
<parameter name="error" type="int">
</parameter>
</method>
-<method name="onInit"
+<method name="onEvent"
return="void"
abstract="true"
native="false"
@@ -135037,6 +135213,10 @@
deprecated="not deprecated"
visibility="public"
>
+<parameter name="eventType" type="int">
+</parameter>
+<parameter name="params" type="android.os.Bundle">
+</parameter>
</method>
<method name="onPartialResults"
return="void"
@@ -135122,10 +135302,6 @@
>
<parameter name="context" type="android.content.Context">
</parameter>
-<parameter name="listener" type="android.speech.RecognitionListener">
-</parameter>
-<parameter name="recognizerIntent" type="android.content.Intent">
-</parameter>
</method>
<method name="destroy"
return="void"
@@ -135151,6 +135327,19 @@
<parameter name="context" type="android.content.Context">
</parameter>
</method>
+<method name="setRecognitionListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.speech.RecognitionListener">
+</parameter>
+</method>
<method name="startListening"
return="void"
abstract="false"
@@ -135175,7 +135364,7 @@
visibility="public"
>
</method>
-<field name="AUDIO_ERROR"
+<field name="ERROR_AUDIO"
type="int"
transient="false"
volatile="false"
@@ -135186,7 +135375,7 @@
visibility="public"
>
</field>
-<field name="CLIENT_ERROR"
+<field name="ERROR_CLIENT"
type="int"
transient="false"
volatile="false"
@@ -135197,7 +135386,7 @@
visibility="public"
>
</field>
-<field name="MANAGER_NOT_INITIALIZED_ERROR"
+<field name="ERROR_INSUFFICIENT_PERMISSIONS"
type="int"
transient="false"
volatile="false"
@@ -135208,7 +135397,7 @@
visibility="public"
>
</field>
-<field name="NETWORK_ERROR"
+<field name="ERROR_NETWORK"
type="int"
transient="false"
volatile="false"
@@ -135219,7 +135408,7 @@
visibility="public"
>
</field>
-<field name="NETWORK_TIMEOUT_ERROR"
+<field name="ERROR_NETWORK_TIMEOUT"
type="int"
transient="false"
volatile="false"
@@ -135230,7 +135419,7 @@
visibility="public"
>
</field>
-<field name="NO_MATCH_ERROR"
+<field name="ERROR_NO_MATCH"
type="int"
transient="false"
volatile="false"
@@ -135241,18 +135430,7 @@
visibility="public"
>
</field>
-<field name="RECOGNITION_RESULTS_STRING_ARRAY"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""recognition_results_string_array""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="SERVER_BUSY_ERROR"
+<field name="ERROR_RECOGNIZER_BUSY"
type="int"
transient="false"
volatile="false"
@@ -135263,7 +135441,7 @@
visibility="public"
>
</field>
-<field name="SERVER_ERROR"
+<field name="ERROR_SERVER"
type="int"
transient="false"
volatile="false"
@@ -135274,7 +135452,7 @@
visibility="public"
>
</field>
-<field name="SPEECH_TIMEOUT_ERROR"
+<field name="ERROR_SPEECH_TIMEOUT"
type="int"
transient="false"
volatile="false"
@@ -135285,6 +135463,213 @@
visibility="public"
>
</field>
+<field name="RESULTS_RECOGNITION"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""results_recognition""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="RecognitionService"
+ extends="android.app.Service"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="RecognitionService"
+ type="android.speech.RecognitionService"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onBind"
+ return="android.os.IBinder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onCancel"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="listener" type="android.speech.RecognitionService.Callback">
+</parameter>
+</method>
+<method name="onStartListening"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="recognizerIntent" type="android.content.Intent">
+</parameter>
+<parameter name="listener" type="android.speech.RecognitionService.Callback">
+</parameter>
+</method>
+<method name="onStopListening"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="listener" type="android.speech.RecognitionService.Callback">
+</parameter>
+</method>
+</class>
+<class name="RecognitionService.Callback"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="beginningOfSpeech"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="bufferReceived"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="buffer" type="byte[]">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="endOfSpeech"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="error"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="int">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="partialResults"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="partialResults" type="android.os.Bundle">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="readyForSpeech"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="params" type="android.os.Bundle">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="results"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="results" type="android.os.Bundle">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="rmsChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rmsdB" type="float">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
</class>
<class name="RecognizerIntent"
extends="java.lang.Object"
@@ -136128,126 +136513,6 @@
</method>
</interface>
</package>
-<package name="android.storage"
->
-<interface name="StorageEventListener"
- abstract="true"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="onMediaInserted"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="label" type="java.lang.String">
-</parameter>
-<parameter name="path" type="java.lang.String">
-</parameter>
-<parameter name="major" type="int">
-</parameter>
-<parameter name="minor" type="int">
-</parameter>
-</method>
-<method name="onMediaRemoved"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="label" type="java.lang.String">
-</parameter>
-<parameter name="path" type="java.lang.String">
-</parameter>
-<parameter name="major" type="int">
-</parameter>
-<parameter name="minor" type="int">
-</parameter>
-<parameter name="clean" type="boolean">
-</parameter>
-</method>
-<method name="onShareAvailabilityChanged"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="method" type="java.lang.String">
-</parameter>
-<parameter name="available" type="boolean">
-</parameter>
-</method>
-<method name="onVolumeStateChanged"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="label" type="java.lang.String">
-</parameter>
-<parameter name="path" type="java.lang.String">
-</parameter>
-<parameter name="oldState" type="java.lang.String">
-</parameter>
-<parameter name="newState" type="java.lang.String">
-</parameter>
-</method>
-</interface>
-<class name="StorageManager"
- extends="java.lang.Object"
- abstract="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="registerListener"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="listener" type="android.storage.StorageEventListener">
-</parameter>
-</method>
-<method name="unregisterListener"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="listener" type="android.storage.StorageEventListener">
-</parameter>
-</method>
-</class>
-</package>
<package name="android.telephony"
>
<class name="CellLocation"
@@ -159338,6 +159603,21 @@
</parameter>
</method>
<method name="tokenize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+<parameter name="out" type="java.util.Collection<android.text.util.Rfc822Token>">
+</parameter>
+</method>
+<method name="tokenize"
return="android.text.util.Rfc822Token[]"
abstract="false"
native="false"
@@ -186327,6 +186607,108 @@
</parameter>
</method>
</class>
+<class name="ConsoleMessage"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ConsoleMessage"
+ type="android.webkit.ConsoleMessage"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="message" type="java.lang.String">
+</parameter>
+<parameter name="sourceId" type="java.lang.String">
+</parameter>
+<parameter name="lineNumber" type="int">
+</parameter>
+<parameter name="msgLevel" type="android.webkit.ConsoleMessage.MessageLevel">
+</parameter>
+</constructor>
+<method name="lineNumber"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="message"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="messageLevel"
+ return="android.webkit.ConsoleMessage.MessageLevel"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="sourceId"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="ConsoleMessage.MessageLevel"
+ extends="java.lang.Enum"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="valueOf"
+ return="android.webkit.ConsoleMessage.MessageLevel"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
+<method name="values"
+ return="android.webkit.ConsoleMessage.MessageLevel[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
<class name="CookieManager"
extends="java.lang.Object"
abstract="false"
@@ -187815,7 +188197,7 @@
synchronized="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="message" type="java.lang.String">
@@ -187825,6 +188207,19 @@
<parameter name="sourceID" type="java.lang.String">
</parameter>
</method>
+<method name="onConsoleMessage"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="consoleMessage" type="android.webkit.ConsoleMessage">
+</parameter>
+</method>
<method name="onCreateWindow"
return="boolean"
abstract="false"
@@ -192095,6 +192490,49 @@
<parameter name="mode" type="int">
</parameter>
</method>
+<method name="smoothScrollBy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="distance" type="int">
+</parameter>
+<parameter name="duration" type="int">
+</parameter>
+</method>
+<method name="smoothScrollToPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="smoothScrollToPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+<parameter name="boundPosition" type="int">
+</parameter>
+</method>
<method name="verifyDrawable"
return="boolean"
abstract="false"
@@ -200347,223 +200785,6 @@
</parameter>
</method>
</interface>
-<class name="NumberPicker"
- extends="android.widget.LinearLayout"
- abstract="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<constructor name="NumberPicker"
- type="android.widget.NumberPicker"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-</constructor>
-<constructor name="NumberPicker"
- type="android.widget.NumberPicker"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-<parameter name="attrs" type="android.util.AttributeSet">
-</parameter>
-</constructor>
-<method name="changeCurrent"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-<parameter name="current" type="int">
-</parameter>
-</method>
-<method name="getBeginRange"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-</method>
-<method name="getCurrent"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getEndRange"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-</method>
-<method name="setCurrent"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="current" type="int">
-</parameter>
-</method>
-<method name="setFormatter"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="formatter" type="android.widget.NumberPicker.Formatter">
-</parameter>
-</method>
-<method name="setOnChangeListener"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="listener" type="android.widget.NumberPicker.OnChangedListener">
-</parameter>
-</method>
-<method name="setRange"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="start" type="int">
-</parameter>
-<parameter name="end" type="int">
-</parameter>
-</method>
-<method name="setRange"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="start" type="int">
-</parameter>
-<parameter name="end" type="int">
-</parameter>
-<parameter name="displayedValues" type="java.lang.String[]">
-</parameter>
-</method>
-<method name="setSpeed"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="speed" type="long">
-</parameter>
-</method>
-<field name="TWO_DIGIT_FORMATTER"
- type="android.widget.NumberPicker.Formatter"
- transient="false"
- volatile="false"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-</class>
-<interface name="NumberPicker.Formatter"
- abstract="true"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="toString"
- return="java.lang.String"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="value" type="int">
-</parameter>
-</method>
-</interface>
-<interface name="NumberPicker.OnChangedListener"
- abstract="true"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="onChanged"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="picker" type="android.widget.NumberPicker">
-</parameter>
-<parameter name="oldVal" type="int">
-</parameter>
-<parameter name="newVal" type="int">
-</parameter>
-</method>
-</interface>
<class name="PopupWindow"
extends="java.lang.Object"
abstract="false"
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 8c15d0b..acfbb07 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -315,7 +315,7 @@
for (RestoreSet s : sets) {
if (s.token == token) {
System.out.println("Scheduling restore: " + s.name);
- didRestore = (mRestore.performRestore(token, observer) == 0);
+ didRestore = (mRestore.restoreAll(token, observer) == 0);
break;
}
}
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 79bda74..1e8555b 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -78,6 +78,30 @@
return delete_dir_contents(pkgdir, 1, 0);
}
+int renamepkg(const char *oldpkgname, const char *newpkgname, int encrypted_fs_flag)
+{
+ char oldpkgdir[PKG_PATH_MAX];
+ char newpkgdir[PKG_PATH_MAX];
+
+ if (encrypted_fs_flag == USE_UNENCRYPTED_FS) {
+ if (create_pkg_path(oldpkgdir, PKG_DIR_PREFIX, oldpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ if (create_pkg_path(newpkgdir, PKG_DIR_PREFIX, newpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ } else {
+ if (create_pkg_path(oldpkgdir, PKG_SEC_DIR_PREFIX, oldpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ if (create_pkg_path(newpkgdir, PKG_SEC_DIR_PREFIX, newpkgname, PKG_DIR_POSTFIX))
+ return -1;
+ }
+
+ if (rename(oldpkgdir, newpkgdir) < 0) {
+ LOGE("cannot rename dir '%s' to '%s': %s\n", oldpkgdir, newpkgdir, strerror(errno));
+ return -errno;
+ }
+ return 0;
+}
+
int delete_user_data(const char *pkgname, int encrypted_fs_flag)
{
char pkgdir[PKG_PATH_MAX];
@@ -636,3 +660,203 @@
}
return -1;
}
+
+int create_move_path(char path[PKG_PATH_MAX],
+ const char* prefix,
+ const char* pkgname,
+ const char* leaf)
+{
+ if ((strlen(prefix) + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) {
+ return -1;
+ }
+
+ sprintf(path, "%s%s/%s", prefix, pkgname, leaf);
+ return 0;
+}
+
+void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid)
+{
+ while (path[basepos] != 0) {
+ if (path[basepos] == '/') {
+ path[basepos] = 0;
+ LOGI("Making directory: %s\n", path);
+ if (mkdir(path, mode) == 0) {
+ chown(path, uid, gid);
+ }
+ path[basepos] = '/';
+ basepos++;
+ }
+ basepos++;
+ }
+}
+
+int movefiles()
+{
+ DIR *d;
+ int dfd, subfd;
+ struct dirent *de;
+ struct stat s;
+ char buf[PKG_PATH_MAX+1];
+ int bufp, bufe, bufi, readlen;
+
+ char srcpkg[PKG_NAME_MAX];
+ char dstpkg[PKG_NAME_MAX];
+ char srcpath[PKG_PATH_MAX];
+ char dstpath[PKG_PATH_MAX];
+ int dstuid, dstgid;
+ int hasspace;
+
+ d = opendir(UPDATE_COMMANDS_DIR_PREFIX);
+ if (d == NULL) {
+ goto done;
+ }
+ dfd = dirfd(d);
+
+ /* Iterate through all files in the directory, executing the
+ * file movements requested there-in.
+ */
+ while ((de = readdir(d))) {
+ const char *name = de->d_name;
+
+ if (de->d_type == DT_DIR) {
+ continue;
+ } else {
+ subfd = openat(dfd, name, O_RDONLY);
+ if (subfd < 0) {
+ LOGW("Unable to open update commands at %s%s\n",
+ UPDATE_COMMANDS_DIR_PREFIX, name);
+ continue;
+ }
+
+ bufp = 0;
+ bufe = 0;
+ buf[PKG_PATH_MAX] = 0;
+ srcpkg[0] = dstpkg[0] = 0;
+ while (1) {
+ bufi = bufp;
+ while (bufi < bufe && buf[bufi] != '\n') {
+ bufi++;
+ }
+ if (bufi < bufe) {
+ buf[bufi] = 0;
+ LOGV("Processing line: %s\n", buf+bufp);
+ hasspace = 0;
+ while (bufp < bufi && isspace(buf[bufp])) {
+ hasspace = 1;
+ bufp++;
+ }
+ if (buf[bufp] == '#' || bufp == bufi) {
+ // skip comments and empty lines.
+ } else if (hasspace) {
+ if (dstpkg[0] == 0) {
+ LOGW("Path before package line in %s%s: %s\n",
+ UPDATE_COMMANDS_DIR_PREFIX, name, buf+bufp);
+ } else if (srcpkg[0] == 0) {
+ // Skip -- source package no longer exists.
+ } else {
+ LOGV("Move file: %s (from %s to %s)\n", buf+bufp, srcpkg, dstpkg);
+ if (!create_move_path(srcpath, PKG_DIR_PREFIX, srcpkg, buf+bufp) &&
+ !create_move_path(dstpath, PKG_DIR_PREFIX, dstpkg, buf+bufp)) {
+ LOGI("Renaming %s to %s (uid %d)\n", srcpath, dstpath, dstuid);
+ mkinnerdirs(dstpath, strlen(dstpath)-(bufi-bufp),
+ S_IRWXU|S_IRWXG|S_IXOTH, dstuid, dstgid);
+ if (rename(srcpath, dstpath) >= 0) {
+ if (chown(dstpath, dstuid, dstgid) < 0) {
+ LOGE("cannot chown %s: %s\n", dstpath, strerror(errno));
+ unlink(dstpath);
+ }
+ } else {
+ LOGW("Unable to rename %s to %s: %s\n",
+ srcpath, dstpath, strerror(errno));
+ }
+ }
+ }
+ } else {
+ char* div = strchr(buf+bufp, ':');
+ if (div == NULL) {
+ LOGW("Bad package spec in %s%s; no ':' sep: %s\n",
+ UPDATE_COMMANDS_DIR_PREFIX, name, buf+bufp);
+ } else {
+ *div = 0;
+ div++;
+ if (strlen(buf+bufp) < PKG_NAME_MAX) {
+ strcpy(dstpkg, buf+bufp);
+ } else {
+ srcpkg[0] = dstpkg[0] = 0;
+ LOGW("Package name too long in %s%s: %s\n",
+ UPDATE_COMMANDS_DIR_PREFIX, name, buf+bufp);
+ }
+ if (strlen(div) < PKG_NAME_MAX) {
+ strcpy(srcpkg, div);
+ } else {
+ srcpkg[0] = dstpkg[0] = 0;
+ LOGW("Package name too long in %s%s: %s\n",
+ UPDATE_COMMANDS_DIR_PREFIX, name, div);
+ }
+ if (srcpkg[0] != 0) {
+ if (!create_pkg_path(srcpath, PKG_DIR_PREFIX, srcpkg,
+ PKG_DIR_POSTFIX)) {
+ if (lstat(srcpath, &s) < 0) {
+ // Package no longer exists -- skip.
+ srcpkg[0] = 0;
+ }
+ } else {
+ srcpkg[0] = 0;
+ LOGW("Can't create path %s in %s%s\n",
+ div, UPDATE_COMMANDS_DIR_PREFIX, name);
+ }
+ if (srcpkg[0] != 0) {
+ if (!create_pkg_path(dstpath, PKG_DIR_PREFIX, dstpkg,
+ PKG_DIR_POSTFIX)) {
+ if (lstat(dstpath, &s) == 0) {
+ dstuid = s.st_uid;
+ dstgid = s.st_gid;
+ } else {
+ srcpkg[0] = 0;
+ LOGW("Can't stat path %s in %s%s: %s\n",
+ dstpath, UPDATE_COMMANDS_DIR_PREFIX,
+ name, strerror(errno));
+ }
+ } else {
+ srcpkg[0] = 0;
+ LOGW("Can't create path %s in %s%s\n",
+ div, UPDATE_COMMANDS_DIR_PREFIX, name);
+ }
+ }
+ LOGV("Transfering from %s to %s: uid=%d\n",
+ srcpkg, dstpkg, dstuid);
+ }
+ }
+ }
+ bufp = bufi+1;
+ } else {
+ if (bufp == 0) {
+ if (bufp < bufe) {
+ LOGW("Line too long in %s%s, skipping: %s\n",
+ UPDATE_COMMANDS_DIR_PREFIX, name, buf);
+ }
+ } else if (bufp < bufe) {
+ memcpy(buf, buf+bufp, bufe-bufp);
+ bufe -= bufp;
+ bufp = 0;
+ }
+ readlen = read(subfd, buf+bufe, PKG_PATH_MAX-bufe);
+ if (readlen < 0) {
+ LOGW("Failure reading update commands in %s%s: %s\n",
+ UPDATE_COMMANDS_DIR_PREFIX, name, strerror(errno));
+ break;
+ } else if (readlen == 0) {
+ break;
+ }
+ bufe += readlen;
+ buf[bufe] = 0;
+ LOGV("Read buf: %s\n", buf);
+ }
+ }
+ close(subfd);
+ }
+ }
+ closedir(d);
+done:
+ return 0;
+}
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index 5bc6c86..882c493 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -53,6 +53,11 @@
return uninstall(arg[0], atoi(arg[1])); /* pkgname */
}
+static int do_rename(char **arg, char reply[REPLY_MAX])
+{
+ return renamepkg(arg[0], arg[1], atoi(arg[2])); /* oldpkgname, newpkgname */
+}
+
static int do_free_cache(char **arg, char reply[REPLY_MAX]) /* TODO int:free_size */
{
return free_cache(atoi(arg[0])); /* free_size */
@@ -87,6 +92,11 @@
return delete_user_data(arg[0], atoi(arg[1])); /* pkgname */
}
+static int do_movefiles(char **arg, char reply[REPLY_MAX])
+{
+ return movefiles();
+}
+
struct cmdinfo {
const char *name;
unsigned numargs;
@@ -100,11 +110,13 @@
{ "movedex", 2, do_move_dex },
{ "rmdex", 1, do_rm_dex },
{ "remove", 2, do_remove },
+ { "rename", 3, do_rename },
{ "freecache", 1, do_free_cache },
{ "rmcache", 2, do_rm_cache },
{ "protect", 2, do_protect },
{ "getsize", 4, do_get_size },
{ "rmuserdata", 2, do_rm_user_data },
+ { "movefiles", 0, do_movefiles },
};
static int readx(int s, void *_buf, int count)
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 35a173e..92ae310 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -73,6 +73,7 @@
#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/"
#define DALVIK_CACHE_POSTFIX "/classes.dex"
+#define UPDATE_COMMANDS_DIR_PREFIX "/system/etc/updatecmds/"
#define PKG_NAME_MAX 128 /* largest allowed package name */
#define PKG_PATH_MAX 256 /* max size of any path we use */
@@ -97,6 +98,7 @@
int install(const char *pkgname, int encrypted_fs_flag, uid_t uid, gid_t gid);
int uninstall(const char *pkgname, int encrypted_fs_flag);
+int renamepkg(const char *oldpkgname, const char *newpkgname, int encrypted_fs_flag);
int delete_user_data(const char *pkgname, int encrypted_fs_flag);
int delete_cache(const char *pkgname, int encrypted_fs_flag);
int move_dex(const char *src, const char *dst);
@@ -106,3 +108,4 @@
int *codesize, int *datasize, int *cachesize, int encrypted_fs_flag);
int free_cache(int free_size);
int dexopt(const char *apk_path, uid_t uid, int is_public);
+int movefiles();
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index 2ec0b70..845c854 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -106,6 +106,9 @@
sp<MediaExtractor> extractor =
MediaExtractor::Create(new FileSource(filename));
+ if (extractor == NULL) {
+ return NULL;
+ }
size_t num_tracks = extractor->countTracks();
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index e65cdf1..f7cb2273 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -431,6 +431,10 @@
mediaSource = new JPEGSource(dataSource);
} else {
sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+ if (extractor == NULL) {
+ fprintf(stderr, "could not create data source\n");
+ return -1;
+ }
size_t numTracks = extractor->countTracks();
diff --git a/common/java/com/android/common/Base64.java b/common/java/com/android/common/Base64.java
new file mode 100644
index 0000000..771875c
--- /dev/null
+++ b/common/java/com/android/common/Base64.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.common;
+
+/**
+ * Utilities for encoding and decoding the Base64 encoding. See RFCs
+ * 2045 and 3548.
+ */
+public class Base64 {
+ /**
+ * Default values for encoder/decoder flags.
+ */
+ public static final int DEFAULT = 0;
+
+ /**
+ * Encoder flag bit to indicate you want the padding '='
+ * characters at the end (if any) to be omitted.
+ */
+ public static final int NO_PADDING = 1;
+
+ /**
+ * Encoder flag bit to indicate you want all line terminators to
+ * be omitted (ie, the output will be on one long line).
+ */
+ public static final int NO_WRAP = 2;
+
+ /**
+ * Encoder flag bit to indicate you want lines to be ended with
+ * CRLF instead of just LF.
+ */
+ public static final int CRLF = 4;
+
+ /**
+ * Encoder/decoder flag bit to indicate using the "web safe"
+ * variant of Base64 (see RFC 3548 section 4) where '-' and '_'
+ * are used in place of '+' and '/'.
+ */
+ public static final int WEB_SAFE = 8;
+
+ /**
+ * Lookup table for turning bytes into their position in the
+ * Base64 alphabet.
+ */
+ private static final int DECODE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /**
+ * Decode lookup table for the "web safe" variant (RFC 3548
+ * sec. 4) where - and _ replace + and /.
+ */
+ private static final int DECODE_WEBSAFE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /** Non-data values in the DECODE arrays. */
+ private static final int SKIP = -1;
+ private static final int EQUALS = -2;
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the input String to decode, which is converted to
+ * bytes using the default charset
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(String str, int flags) {
+ return decode(str.getBytes(), flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the input array to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int flags) {
+ return decode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the data to decode
+ * @param offset the position within the input array at which to start
+ * @param len the number of bytes of input to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int offset, int len, int flags) {
+ int p = offset;
+ // Allocate space for the most data the input could represent.
+ // (It could contain less if it contains whitespace, etc.)
+ byte[] output = new byte[len*3/4];
+ len += offset;
+ int op = 0;
+
+ final int[] decode = ((flags & WEB_SAFE) == 0) ?
+ DECODE : DECODE_WEBSAFE;
+
+ int state = 0;
+ int value = 0;
+
+ while (p < len) {
+
+ // Try the fast path: we're starting a new tuple and the
+ // next four bytes of the input stream are all data
+ // bytes. This corresponds to going through states
+ // 0-1-2-3-0. We expect to use this method for most of
+ // the data.
+ //
+ // If any of the next four bytes of input are non-data
+ // (whitespace, etc.), value will end up negative. (All
+ // the non-data values in decode are small negative
+ // numbers, so shifting any of them up and or'ing them
+ // together will result in a value with its top bit set.)
+ //
+ // You can remove this whole block and the output should
+ // be the same, just slower.
+ if (state == 0 && p+4 <= len &&
+ (value = ((decode[input[p] & 0xff] << 18) |
+ (decode[input[p+1] & 0xff] << 12) |
+ (decode[input[p+2] & 0xff] << 6) |
+ (decode[input[p+3] & 0xff]))) >= 0) {
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ p += 4;
+ continue;
+ }
+
+ // The fast path isn't available -- either we've read a
+ // partial tuple, or the next four input bytes aren't all
+ // data, or whatever. Fall back to the slower state
+ // machine implementation.
+ //
+ // States 0-3 are reading through the next input tuple.
+ // State 4 is having read one '=' and expecting exactly
+ // one more.
+ // State 5 is expecting no more data or padding characters
+ // in the input.
+
+ int d = decode[input[p++] & 0xff];
+
+ switch (state) {
+ case 0:
+ if (d >= 0) {
+ value = d;
+ ++state;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 1:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 2:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect exactly one more padding character.
+ output[op++] = (byte) (value >> 4);
+ state = 4;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 3:
+ if (d >= 0) {
+ // Emit the output triple and return to state 0.
+ value = (value << 6) | d;
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ state = 0;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect no further data or padding characters.
+ output[op+1] = (byte) (value >> 2);
+ output[op] = (byte) (value >> 10);
+ op += 2;
+ state = 5;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 4:
+ if (d == EQUALS) {
+ ++state;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 5:
+ if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+ }
+ }
+
+ // Done reading input. Now figure out where we are left in
+ // the state machine and finish up.
+
+ switch (state) {
+ case 0:
+ // Output length is a multiple of three. Fine.
+ break;
+ case 1:
+ // Read one extra input byte, which isn't enough to
+ // make another output byte. Illegal.
+ throw new IllegalArgumentException("bad base-64");
+ case 2:
+ // Read two extra input bytes, enough to emit 1 more
+ // output byte. Fine.
+ output[op++] = (byte) (value >> 4);
+ break;
+ case 3:
+ // Read three extra input bytes, enough to emit 2 more
+ // output bytes. Fine.
+ output[op+1] = (byte) (value >> 2);
+ output[op] = (byte) (value >> 10);
+ op += 2;
+ break;
+ case 4:
+ // Read one padding '=' when we expected 2. Illegal.
+ throw new IllegalArgumentException("bad base-64");
+ case 5:
+ // Read all the padding '='s we expected and no more.
+ // Fine.
+ break;
+ }
+
+ // Maybe we got lucky and allocated exactly enough output space.
+ if (op == output.length) {
+ return output;
+ }
+
+ // Need to shorten the array, so allocate a new one of the
+ // right size and copy.
+ byte[] temp = new byte[op];
+ System.arraycopy(output, 0, temp, 0, op);
+ return temp;
+ }
+
+ /**
+ * Emit a new line every this many output tuples. Corresponds to
+ * a 76-character line length (the maximum allowable according to
+ * RFC 2045).
+ */
+ private static final int LINE_GROUPS = 19;
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/',
+ };
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE_WEBSAFE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '-', '_',
+ };
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int flags) {
+ return new String(encode(input, flags));
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int offset, int len, int flags) {
+ return new String(encode(input, offset, len, flags));
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int flags) {
+ return encode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int offset, int len, int flags) {
+ final boolean do_padding = (flags & NO_PADDING) == 0;
+ final boolean do_newline = (flags & NO_WRAP) == 0;
+ final boolean do_cr = (flags & CRLF) != 0;
+
+ final byte[] encode = ((flags & WEB_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
+
+ // Compute the exact length of the array we will produce.
+ int output_len = len / 3 * 4;
+
+ // Account for the tail of the data and the padding bytes, if any.
+ if (do_padding) {
+ if (len % 3 > 0) {
+ output_len += 4;
+ }
+ } else {
+ switch (len % 3) {
+ case 0: break;
+ case 1: output_len += 2; break;
+ case 2: output_len += 3; break;
+ }
+ }
+
+ // Account for the newlines, if any.
+ if (do_newline && len > 0) {
+ output_len += (((len-1) / (3 * LINE_GROUPS)) + 1) * (do_cr ? 2 : 1);
+ }
+
+ int op = 0;
+ byte[] output = new byte[output_len];
+
+ // The main loop, turning 3 input bytes into 4 output bytes on
+ // each iteration.
+ int count = do_newline ? LINE_GROUPS : -1;
+ int p = offset;
+ len += offset;
+ while (p+3 <= len) {
+ int v = ((input[p++] & 0xff) << 16) |
+ ((input[p++] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ output[op++] = encode[(v >> 18) & 0x3f];
+ output[op++] = encode[(v >> 12) & 0x3f];
+ output[op++] = encode[(v >> 6) & 0x3f];
+ output[op++] = encode[v & 0x3f];
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+
+ // Finish up the tail of the input.
+ if (p == len-1) {
+ int v = (input[p] & 0xff) << 4;
+ output[op++] = encode[(v >> 6) & 0x3f];
+ output[op++] = encode[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (p == len-2) {
+ int v = ((input[p] & 0xff) << 10) | ((input[p+1] & 0xff) << 2);
+ output[op++] = encode[(v >> 12) & 0x3f];
+ output[op++] = encode[(v >> 6) & 0x3f];
+ output[op++] = encode[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (do_newline && op > 0 && count != LINE_GROUPS) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+
+ assert op == output.length;
+ return output;
+ }
+
+ private Base64() { } // don't instantiate
+}
diff --git a/common/java/com/android/common/OperationScheduler.java b/common/java/com/android/common/OperationScheduler.java
index 71b22ce..c7b12d3 100644
--- a/common/java/com/android/common/OperationScheduler.java
+++ b/common/java/com/android/common/OperationScheduler.java
@@ -192,7 +192,6 @@
/**
* Forbid any operations until after a certain (absolute) time.
- * Commonly used when a server returns a "Retry-After:" type directive.
* Limited by {@link #Options.maxMoratoriumMillis}.
*
* @param millis wall clock time ({@link System#currentTimeMillis()}) to
@@ -206,6 +205,29 @@
}
/**
+ * Forbid any operations until after a certain time, as specified in
+ * the format used by the HTTP "Retry-After" header.
+ * Limited by {@link #Options.maxMoratoriumMillis}.
+ *
+ * @param retryAfter moratorium time in HTTP format
+ * @return true if a time was successfully parsed
+ */
+ public boolean setMoratoriumTimeHttp(String retryAfter) {
+ try {
+ long ms = Long.valueOf(retryAfter) * 1000;
+ setMoratoriumTimeMillis(ms + System.currentTimeMillis());
+ return true;
+ } catch (NumberFormatException nfe) {
+ try {
+ setMoratoriumTimeMillis(HttpDateTime.parse(retryAfter));
+ return true;
+ } catch (IllegalArgumentException iae) {
+ return false;
+ }
+ }
+ }
+
+ /**
* Enable or disable all operations. When disabled, all calls to
* {@link #getNextTimeMillis()} return {@link Long#MAX_VALUE}.
* Commonly used when data network availability goes up and down.
diff --git a/common/tests/src/com/android/common/Base64Test.java b/common/tests/src/com/android/common/Base64Test.java
new file mode 100644
index 0000000..5c9712a
--- /dev/null
+++ b/common/tests/src/com/android/common/Base64Test.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.common;
+
+import junit.framework.TestCase;
+
+public class Base64Test extends TestCase {
+ private static final String TAG = "B64Test";
+
+ /** Decodes a string, returning a string. */
+ private String decodeString(String in) throws Exception {
+ byte[] out = Base64.decode(in, 0);
+ return new String(out);
+ }
+
+ /**
+ * Encodes the string 'in' using 'flags'. Asserts that decoding
+ * gives the same string. Returns the encoded string.
+ */
+ private String encodeToString(String in, int flags) throws Exception {
+ String b64 = Base64.encodeToString(in.getBytes(), flags);
+ String dec = decodeString(b64);
+ assertEquals(in, dec);
+ return b64;
+ }
+
+ /** Assert that decoding 'in' throws IllegalArgumentException. */
+ private void assertBad(String in) throws Exception {
+ try {
+ byte[] out = Base64.decode(in, 0);
+ fail("should have failed to decode");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ /** Assert that actual equals the first len bytes of expected. */
+ private void assertEquals(byte[] expected, int len, byte[] actual) {
+ assertEquals(len, actual.length);
+ for (int i = 0; i < len; ++i) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ public void testDecodeExtraChars() throws Exception {
+ // padding 0
+ assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
+ assertBad("aGVsbG8sIHdvcmxk=");
+ assertBad("aGVsbG8sIHdvcmxk==");
+ assertBad("aGVsbG8sIHdvcmxk =");
+ assertBad("aGVsbG8sIHdvcmxk = = ");
+ assertEquals("hello, world", decodeString(" aGVs bG8s IHdv cmxk "));
+ assertEquals("hello, world", decodeString(" aGV sbG8 sIHd vcmx k "));
+ assertEquals("hello, world", decodeString(" aG VsbG 8sIH dvcm xk "));
+ assertEquals("hello, world", decodeString(" a GVsb G8sI Hdvc mxk "));
+ assertEquals("hello, world", decodeString(" a G V s b G 8 s I H d v c m x k "));
+ assertEquals("hello, world", decodeString("_a*G_V*s_b*G_8*s_I*H_d*v_c*m_x*k_"));
+ assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
+
+ // padding 1
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE"));
+ assertBad("aGVsbG8sIHdvcmxkPyE==");
+ assertBad("aGVsbG8sIHdvcmxkPyE ==");
+ assertBad("aGVsbG8sIHdvcmxkPyE = = ");
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E"));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E ="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E = "));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
+
+ // padding 2
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg=="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg"));
+ assertBad("aGVsbG8sIHdvcmxkLg=");
+ assertBad("aGVsbG8sIHdvcmxkLg =");
+ assertBad("aGVsbG8sIHdvcmxkLg = ");
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g=="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g"));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g =="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g = = "));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
+ }
+
+ private static final byte[] BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd,
+ (byte) 0xcc, (byte) 0xbb, (byte) 0xaa,
+ (byte) 0x99, (byte) 0x88, (byte) 0x77 };
+
+ public void testBinaryDecode() throws Exception {
+ assertEquals(BYTES, 0, Base64.decode("", 0));
+ assertEquals(BYTES, 1, Base64.decode("/w==", 0));
+ assertEquals(BYTES, 2, Base64.decode("/+4=", 0));
+ assertEquals(BYTES, 3, Base64.decode("/+7d", 0));
+ assertEquals(BYTES, 4, Base64.decode("/+7dzA==", 0));
+ assertEquals(BYTES, 5, Base64.decode("/+7dzLs=", 0));
+ assertEquals(BYTES, 6, Base64.decode("/+7dzLuq", 0));
+ assertEquals(BYTES, 7, Base64.decode("/+7dzLuqmQ==", 0));
+ assertEquals(BYTES, 8, Base64.decode("/+7dzLuqmYg=", 0));
+ }
+
+ public void testWebSafe() throws Exception {
+ assertEquals(BYTES, 0, Base64.decode("", Base64.WEB_SAFE));
+ assertEquals(BYTES, 1, Base64.decode("_w==", Base64.WEB_SAFE));
+ assertEquals(BYTES, 2, Base64.decode("_-4=", Base64.WEB_SAFE));
+ assertEquals(BYTES, 3, Base64.decode("_-7d", Base64.WEB_SAFE));
+ assertEquals(BYTES, 4, Base64.decode("_-7dzA==", Base64.WEB_SAFE));
+ assertEquals(BYTES, 5, Base64.decode("_-7dzLs=", Base64.WEB_SAFE));
+ assertEquals(BYTES, 6, Base64.decode("_-7dzLuq", Base64.WEB_SAFE));
+ assertEquals(BYTES, 7, Base64.decode("_-7dzLuqmQ==", Base64.WEB_SAFE));
+ assertEquals(BYTES, 8, Base64.decode("_-7dzLuqmYg=", Base64.WEB_SAFE));
+
+ assertEquals("", Base64.encodeToString(BYTES, 0, 0, Base64.WEB_SAFE));
+ assertEquals("_w==\n", Base64.encodeToString(BYTES, 0, 1, Base64.WEB_SAFE));
+ assertEquals("_-4=\n", Base64.encodeToString(BYTES, 0, 2, Base64.WEB_SAFE));
+ assertEquals("_-7d\n", Base64.encodeToString(BYTES, 0, 3, Base64.WEB_SAFE));
+ assertEquals("_-7dzA==\n", Base64.encodeToString(BYTES, 0, 4, Base64.WEB_SAFE));
+ assertEquals("_-7dzLs=\n", Base64.encodeToString(BYTES, 0, 5, Base64.WEB_SAFE));
+ assertEquals("_-7dzLuq\n", Base64.encodeToString(BYTES, 0, 6, Base64.WEB_SAFE));
+ assertEquals("_-7dzLuqmQ==\n", Base64.encodeToString(BYTES, 0, 7, Base64.WEB_SAFE));
+ assertEquals("_-7dzLuqmYg=\n", Base64.encodeToString(BYTES, 0, 8, Base64.WEB_SAFE));
+ }
+
+ public void testFlags() throws Exception {
+ assertEquals("YQ==\n", encodeToString("a", 0));
+ assertEquals("YQ==", encodeToString("a", Base64.NO_WRAP));
+ assertEquals("YQ\n", encodeToString("a", Base64.NO_PADDING));
+ assertEquals("YQ", encodeToString("a", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YQ==\r\n", encodeToString("a", Base64.CRLF));
+ assertEquals("YQ\r\n", encodeToString("a", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWI=\n", encodeToString("ab", 0));
+ assertEquals("YWI=", encodeToString("ab", Base64.NO_WRAP));
+ assertEquals("YWI\n", encodeToString("ab", Base64.NO_PADDING));
+ assertEquals("YWI", encodeToString("ab", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWI=\r\n", encodeToString("ab", Base64.CRLF));
+ assertEquals("YWI\r\n", encodeToString("ab", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWJj\n", encodeToString("abc", 0));
+ assertEquals("YWJj", encodeToString("abc", Base64.NO_WRAP));
+ assertEquals("YWJj\n", encodeToString("abc", Base64.NO_PADDING));
+ assertEquals("YWJj", encodeToString("abc", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF));
+ assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWJjZA==\n", encodeToString("abcd", 0));
+ assertEquals("YWJjZA==", encodeToString("abcd", Base64.NO_WRAP));
+ assertEquals("YWJjZA\n", encodeToString("abcd", Base64.NO_PADDING));
+ assertEquals("YWJjZA", encodeToString("abcd", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWJjZA==\r\n", encodeToString("abcd", Base64.CRLF));
+ assertEquals("YWJjZA\r\n", encodeToString("abcd", Base64.CRLF | Base64.NO_PADDING));
+ }
+
+ public void testLineLength() throws Exception {
+ String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd";
+ String in_57 = in_56 + "e";
+ String in_58 = in_56 + "ef";
+ String in_59 = in_56 + "efg";
+ String in_60 = in_56 + "efgh";
+ String in_61 = in_56 + "efghi";
+
+ String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi";
+ String out_56 = prefix + "Y2Q=\n";
+ String out_57 = prefix + "Y2Rl\n";
+ String out_58 = prefix + "Y2Rl\nZg==\n";
+ String out_59 = prefix + "Y2Rl\nZmc=\n";
+ String out_60 = prefix + "Y2Rl\nZmdo\n";
+ String out_61 = prefix + "Y2Rl\nZmdoaQ==\n";
+
+ // no newline for an empty input array.
+ assertEquals("", encodeToString("", 0));
+
+ assertEquals(out_56, encodeToString(in_56, 0));
+ assertEquals(out_57, encodeToString(in_57, 0));
+ assertEquals(out_58, encodeToString(in_58, 0));
+ assertEquals(out_59, encodeToString(in_59, 0));
+ assertEquals(out_60, encodeToString(in_60, 0));
+ assertEquals(out_61, encodeToString(in_61, 0));
+
+ assertEquals(out_56.replaceAll("=", ""), encodeToString(in_56, Base64.NO_PADDING));
+ assertEquals(out_57.replaceAll("=", ""), encodeToString(in_57, Base64.NO_PADDING));
+ assertEquals(out_58.replaceAll("=", ""), encodeToString(in_58, Base64.NO_PADDING));
+ assertEquals(out_59.replaceAll("=", ""), encodeToString(in_59, Base64.NO_PADDING));
+ assertEquals(out_60.replaceAll("=", ""), encodeToString(in_60, Base64.NO_PADDING));
+ assertEquals(out_61.replaceAll("=", ""), encodeToString(in_61, Base64.NO_PADDING));
+
+ assertEquals(out_56.replaceAll("\n", ""), encodeToString(in_56, Base64.NO_WRAP));
+ assertEquals(out_57.replaceAll("\n", ""), encodeToString(in_57, Base64.NO_WRAP));
+ assertEquals(out_58.replaceAll("\n", ""), encodeToString(in_58, Base64.NO_WRAP));
+ assertEquals(out_59.replaceAll("\n", ""), encodeToString(in_59, Base64.NO_WRAP));
+ assertEquals(out_60.replaceAll("\n", ""), encodeToString(in_60, Base64.NO_WRAP));
+ assertEquals(out_61.replaceAll("\n", ""), encodeToString(in_61, Base64.NO_WRAP));
+ }
+}
diff --git a/common/tests/src/com/android/common/OperationSchedulerTest.java b/common/tests/src/com/android/common/OperationSchedulerTest.java
index 13f710d..28178b5 100644
--- a/common/tests/src/com/android/common/OperationSchedulerTest.java
+++ b/common/tests/src/com/android/common/OperationSchedulerTest.java
@@ -113,4 +113,32 @@
"OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]",
OperationScheduler.parseOptions("", options).toString());
}
+
+ public void testMoratoriumWithHttpDate() throws Exception {
+ String name = "OperationSchedulerTest.testMoratoriumWithHttpDate";
+ SharedPreferences storage = getContext().getSharedPreferences(name, 0);
+ storage.edit().clear().commit();
+
+ OperationScheduler scheduler = new OperationScheduler(storage);
+ OperationScheduler.Options options = new OperationScheduler.Options();
+
+ long beforeTrigger = System.currentTimeMillis();
+ scheduler.setTriggerTimeMillis(beforeTrigger + 1000000);
+ assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options));
+
+ scheduler.setMoratoriumTimeMillis(beforeTrigger + 2000000);
+ assertEquals(beforeTrigger + 2000000, scheduler.getNextTimeMillis(options));
+
+ long beforeMoratorium = System.currentTimeMillis();
+ assertTrue(scheduler.setMoratoriumTimeHttp("3000"));
+ long afterMoratorium = System.currentTimeMillis();
+ assertTrue(beforeMoratorium + 3000000 <= scheduler.getNextTimeMillis(options));
+ assertTrue(afterMoratorium + 3000000 >= scheduler.getNextTimeMillis(options));
+
+ options.maxMoratoriumMillis = Long.MAX_VALUE / 2;
+ assertTrue(scheduler.setMoratoriumTimeHttp("Fri, 31 Dec 2030 23:59:59 GMT"));
+ assertEquals(1924991999000L, scheduler.getNextTimeMillis(options));
+
+ assertFalse(scheduler.setMoratoriumTimeHttp("not actually a date"));
+ }
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 414d963..652f405 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -271,7 +271,7 @@
}
/**
- * Add an account to the AccountManager's set of known accounts.
+ * Add an account to the AccountManager's set of known accounts.
* <p>
* Requires that the caller has permission
* {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
@@ -560,9 +560,13 @@
* user to enter credentials. If it is able to retrieve the authtoken it will be returned
* in the result.
* <p>
- * If the authenticator needs to prompt the user for credentials it will return an intent for
+ * If the authenticator needs to prompt the user for credentials, rather than returning the
+ * authtoken it will instead return an intent for
* an activity that will do the prompting. If an intent is returned and notifyAuthFailure
- * is true then a notification will be created that launches this intent.
+ * is true then a notification will be created that launches this intent. This intent can be
+ * invoked by the caller directly to start the activity that prompts the user for the
+ * updated credentials. Otherwise this activity will not be run until the user activates
+ * the notification.
* <p>
* This call returns immediately but runs asynchronously and the result is accessed via the
* {@link AccountManagerFuture} that is returned. This future is also passed as the sole
@@ -653,7 +657,7 @@
if (accountType == null) {
Log.e(TAG, "the account must not be null");
// to unblock caller waiting on Future.get()
- set(new Bundle());
+ set(new Bundle());
return;
}
mService.addAcount(mResponse, accountType, authTokenType,
@@ -833,14 +837,11 @@
private void ensureNotOnMainThread() {
final Looper looper = Looper.myLooper();
if (looper != null && looper == mContext.getMainLooper()) {
- // We really want to throw an exception here, but GTalkService exercises this
- // path quite a bit and needs some serious rewrite in order to work properly.
- //noinspection ThrowableInstanceNeverThrow
-// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
-// new Exception());
- // TODO remove the log and throw this exception when the callers are fixed
-// throw new IllegalStateException(
-// "calling this from your main thread can lead to deadlock");
+ final IllegalStateException exception = new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
+ exception);
+ throw exception;
}
}
@@ -905,7 +906,9 @@
private Bundle internalGetResult(Long timeout, TimeUnit unit)
throws OperationCanceledException, IOException, AuthenticatorException {
- ensureNotOnMainThread();
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
try {
if (timeout == null) {
return get();
@@ -1071,7 +1074,9 @@
private T internalGetResult(Long timeout, TimeUnit unit)
throws OperationCanceledException, IOException, AuthenticatorException {
- ensureNotOnMainThread();
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
try {
if (timeout == null) {
return get();
@@ -1372,7 +1377,7 @@
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
// To recover from disk-full.
- intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 10fef0d..56e44c8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -61,6 +61,7 @@
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
+import android.view.ViewRoot;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -291,7 +292,7 @@
if (mAppDir == null) {
if (mSystemContext == null) {
mSystemContext =
- ApplicationContext.createSystemContext(mainThread);
+ ContextImpl.createSystemContext(mainThread);
mSystemContext.getResources().updateConfiguration(
mainThread.getConfiguration(),
mainThread.getDisplayMetricsLocked(false));
@@ -512,7 +513,7 @@
try {
java.lang.ClassLoader cl = getClassLoader();
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(this, null, mActivityThread);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
@@ -1144,7 +1145,7 @@
}
}
- private static ApplicationContext mSystemContext = null;
+ private static ContextImpl mSystemContext = null;
private static final class ActivityRecord {
IBinder token;
@@ -1307,7 +1308,7 @@
}
private static final class ContextCleanupInfo {
- ApplicationContext context;
+ ContextImpl context;
String what;
String who;
}
@@ -1628,7 +1629,7 @@
long dalvikAllocated = dalvikMax - dalvikFree;
long viewInstanceCount = ViewDebug.getViewInstanceCount();
long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
- long appContextInstanceCount = ApplicationContext.getInstanceCount();
+ long appContextInstanceCount = ContextImpl.getInstanceCount();
long activityInstanceCount = Activity.getInstanceCount();
int globalAssetCount = AssetManager.getGlobalAssetCount();
int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
@@ -1813,6 +1814,7 @@
public static final int DESTROY_BACKUP_AGENT = 129;
public static final int SUICIDE = 130;
public static final int REMOVE_PROVIDER = 131;
+ public static final int ENABLE_JIT = 132;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -1848,6 +1850,7 @@
case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
case SUICIDE: return "SUICIDE";
case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
+ case ENABLE_JIT: return "ENABLE_JIT";
}
}
return "(unknown)";
@@ -1965,6 +1968,9 @@
case REMOVE_PROVIDER:
completeRemoveProvider((IContentProvider)msg.obj);
break;
+ case ENABLE_JIT:
+ ensureJitEnabled();
+ break;
}
}
@@ -2000,6 +2006,7 @@
prev.nextIdle = null;
} while (a != null);
}
+ ensureJitEnabled();
return false;
}
}
@@ -2064,6 +2071,7 @@
String mInstrumentationAppPackage = null;
String mInstrumentedAppDir = null;
boolean mSystemThread = false;
+ boolean mJitEnabled = false;
/**
* Activities that are enqueued to be relaunched. This list is accessed
@@ -2245,11 +2253,11 @@
return mBoundApplication.processName;
}
- public ApplicationContext getSystemContext() {
+ public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
- ApplicationContext context =
- ApplicationContext.createSystemContext(this);
+ ContextImpl context =
+ ContextImpl.createSystemContext(this);
PackageInfo info = new PackageInfo(this, "android", context, null);
context.init(info, null, this);
context.getResources().updateConfiguration(
@@ -2264,11 +2272,18 @@
public void installSystemApplicationInfo(ApplicationInfo info) {
synchronized (this) {
- ApplicationContext context = getSystemContext();
+ ContextImpl context = getSystemContext();
context.init(new PackageInfo(this, "android", context, info), null, this);
}
}
+ void ensureJitEnabled() {
+ if (!mJitEnabled) {
+ mJitEnabled = true;
+ dalvik.system.VMRuntime.getRuntime().startJitCompilation();
+ }
+ }
+
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
@@ -2372,7 +2387,7 @@
}
}
- final void scheduleContextCleanup(ApplicationContext context, String who,
+ final void scheduleContextCleanup(ContextImpl context, String who,
String what) {
ContextCleanupInfo cci = new ContextCleanupInfo();
cci.context = context;
@@ -2431,7 +2446,7 @@
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
@@ -2628,7 +2643,7 @@
+ ", comp=" + data.intent.getComponent().toShortString()
+ ", dir=" + packageInfo.getAppDir());
- ApplicationContext context = (ApplicationContext)app.getBaseContext();
+ ContextImpl context = (ContextImpl)app.getBaseContext();
receiver.setOrderedHint(true);
receiver.setResult(data.resultCode, data.resultData,
data.resultExtras);
@@ -2697,7 +2712,7 @@
if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent "
+ data.appInfo.backupAgentName);
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
context.setOuterContext(agent);
agent.attach(context);
@@ -2769,7 +2784,7 @@
try {
if (localLOGV) Log.v(TAG, "Creating service " + data.info.name);
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
Application app = packageInfo.makeApplication(false, mInstrumentation);
@@ -2808,6 +2823,7 @@
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 0, 0, 0);
}
+ ensureJitEnabled();
} catch (RemoteException ex) {
}
} catch (Exception e) {
@@ -2876,6 +2892,7 @@
} catch (RemoteException e) {
// nothing to do.
}
+ ensureJitEnabled();
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
@@ -2893,9 +2910,9 @@
if (localLOGV) Log.v(TAG, "Destroying service " + s);
s.onDestroy();
Context context = s.getBaseContext();
- if (context instanceof ApplicationContext) {
+ if (context instanceof ContextImpl) {
final String who = s.getClassName();
- ((ApplicationContext) context).scheduleFinalCleanup(who, "Service");
+ ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
}
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
@@ -3510,8 +3527,8 @@
// ApplicationContext we need to have it tear down things
// cleanly.
Context c = r.activity.getBaseContext();
- if (c instanceof ApplicationContext) {
- ((ApplicationContext) c).scheduleFinalCleanup(
+ if (c instanceof ContextImpl) {
+ ((ContextImpl) c).scheduleFinalCleanup(
r.activity.getClass().getName(), "Activity");
}
}
@@ -3773,7 +3790,7 @@
Resources.updateSystemConfiguration(config, dm);
- ApplicationContext.ApplicationPackageManager.configurationChanged();
+ ContextImpl.ApplicationPackageManager.configurationChanged();
//Log.i(TAG, "Configuration changed in " + currentPackageName());
{
Iterator<WeakReference<Resources>> it =
@@ -3864,10 +3881,6 @@
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
- // We now rely on this being set by zygote.
- //Process.setGid(data.appInfo.gid);
- //Process.setUid(data.appInfo.uid);
-
// send up app name; do this *before* waiting for debugger
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName);
@@ -3929,7 +3942,7 @@
}
if (data.instrumentationName != null) {
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(data.info, null, this);
InstrumentationInfo ii = null;
try {
@@ -3954,7 +3967,7 @@
instrApp.dataDir = ii.dataDir;
PackageInfo pi = getPackageInfo(instrApp,
appContext.getClassLoader(), false, true);
- ApplicationContext instrContext = new ApplicationContext();
+ ContextImpl instrContext = new ContextImpl();
instrContext.init(pi, null, this);
try {
@@ -3998,6 +4011,9 @@
List<ProviderInfo> providers = data.providers;
if (providers != null) {
installContentProviders(app, providers);
+ // For process that contain content providers, we want to
+ // ensure that the JIT is enabled "at some point".
+ mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
try {
@@ -4303,6 +4319,11 @@
sThreadLocal.set(this);
mSystemThread = system;
if (!system) {
+ ViewRoot.addFirstDrawHandler(new Runnable() {
+ public void run() {
+ ensureJitEnabled();
+ }
+ });
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>");
RuntimeInit.setApplicationObject(mAppThread.asBinder());
IActivityManager mgr = ActivityManagerNative.getDefault();
@@ -4316,7 +4337,7 @@
android.ddm.DdmHandleAppName.setAppName("system_process");
try {
mInstrumentation = new Instrumentation();
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(getSystemContext().mPackageInfo, null, this);
Application app = Instrumentation.newApplication(Application.class, context);
mAllApplications.add(app);
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 53c7935..9082003 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -277,7 +277,26 @@
} catch (RemoteException ex) {
}
}
-
+
+ /**
+ * Set the system wall clock time.
+ * Requires the permission android.permission.SET_TIME.
+ *
+ * @param millis time in milliseconds since the Epoch
+ */
+ public void setTime(long millis) {
+ try {
+ mService.setTime(millis);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Set the system default time zone.
+ * Requires the permission android.permission.SET_TIME_ZONE.
+ *
+ * @param timeZone in the format understood by {@link java.util.TimeZone}
+ */
public void setTimeZone(String timeZone) {
try {
mService.setTimeZone(timeZone);
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ContextImpl.java
similarity index 98%
rename from core/java/android/app/ApplicationContext.java
rename to core/java/android/app/ContextImpl.java
index cf6e0e7..4923eee 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ContextImpl.java
@@ -84,7 +84,7 @@
import android.os.StatFs;
import android.os.Vibrator;
import android.os.FileUtils.FileStatus;
-import android.storage.StorageManager;
+import android.os.storage.StorageManager;
import android.telephony.TelephonyManager;
import android.text.ClipboardManager;
import android.util.AndroidRuntimeException;
@@ -148,10 +148,10 @@
}
/**
- * Common implementation of Context API, which Activity and other application
- * classes inherit.
+ * Common implementation of Context API, which provides the base
+ * context object for Activity and other application components.
*/
-class ApplicationContext extends Context {
+class ContextImpl extends Context {
private final static String TAG = "ApplicationContext";
private final static boolean DEBUG = false;
private final static boolean DEBUG_ICONS = false;
@@ -1328,13 +1328,13 @@
public Context createPackageContext(String packageName, int flags)
throws PackageManager.NameNotFoundException {
if (packageName.equals("system") || packageName.equals("android")) {
- return new ApplicationContext(mMainThread.getSystemContext());
+ return new ContextImpl(mMainThread.getSystemContext());
}
ActivityThread.PackageInfo pi =
mMainThread.getPackageInfo(packageName, flags);
if (pi != null) {
- ApplicationContext c = new ApplicationContext();
+ ContextImpl c = new ContextImpl();
c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
c.init(pi, null, mMainThread, mResources);
if (c.mResources != null) {
@@ -1371,13 +1371,13 @@
return file;
}
- static ApplicationContext createSystemContext(ActivityThread mainThread) {
- ApplicationContext context = new ApplicationContext();
+ static ContextImpl createSystemContext(ActivityThread mainThread) {
+ ContextImpl context = new ContextImpl();
context.init(Resources.getSystem(), mainThread);
return context;
}
- ApplicationContext() {
+ ContextImpl() {
++sInstanceCount;
mOuterContext = this;
}
@@ -1388,7 +1388,7 @@
*
* @param context Existing application context.
*/
- public ApplicationContext(ApplicationContext context) {
+ public ContextImpl(ContextImpl context) {
++sInstanceCount;
mPackageInfo = context.mPackageInfo;
mResources = context.mResources;
@@ -2124,7 +2124,7 @@
}
}
- ApplicationPackageManager(ApplicationContext context,
+ ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;
mPM = pm;
@@ -2166,7 +2166,7 @@
filter, null, null, null);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiverInternal(sPackageRemovedReceiver,
sdFilter, null, null, null);
}
@@ -2189,7 +2189,7 @@
String pkgList[] = null;
String action = intent.getAction();
boolean immediateGc = false;
- if (Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
immediateGc = true;
} else {
@@ -2656,7 +2656,7 @@
}
}
- private final ApplicationContext mContext;
+ private final ContextImpl mContext;
private final IPackageManager mPM;
private static final Object sSync = new Object();
diff --git a/core/java/android/app/DeviceAdmin.java b/core/java/android/app/DeviceAdmin.java
index ecbad01..af9c379 100644
--- a/core/java/android/app/DeviceAdmin.java
+++ b/core/java/android/app/DeviceAdmin.java
@@ -40,7 +40,7 @@
* to the device administrator, as parsed by the {@link DeviceAdminInfo} class.
* A typical file would be:</p>
*
- * {@sample development/samples/ApiDemos/res/xml/sample_device_admin.xml meta_data}
+ * {@sample development/samples/ApiDemos/res/xml/device_admin_sample.xml meta_data}
*/
public class DeviceAdmin extends BroadcastReceiver {
private static String TAG = "DevicePolicy";
diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java
index 08cdd05..847e879 100644
--- a/core/java/android/app/DevicePolicyManager.java
+++ b/core/java/android/app/DevicePolicyManager.java
@@ -328,14 +328,20 @@
}
/**
- * Set the maximum number of failed password attempts that are allowed
- * before the device wipes its data. This is convenience for implementing
- * the corresponding functionality with a combination of watching failed
- * password attempts and calling {@link #wipeData} upon reaching a certain
- * count, and as such requires that you request both
- * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
+ * Setting this to a value greater than zero enables a built-in policy
+ * that will perform a device wipe after too many incorrect
+ * device-unlock passwords have been entered. This built-in policy combines
+ * watching for failed passwords and wiping the device, and requires
+ * that you request both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
* {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}.
*
+ * <p>To implement any other policy (e.g. wiping data for a particular
+ * application only, erasing or revoking credentials, or reporting the
+ * failure to a server), you should implement
+ * {@link DeviceAdmin#onPasswordFailed(Context, android.content.Intent)}
+ * instead. Do not use this API, because if the maximum count is reached,
+ * the device will be wiped immediately, and your callback will not be invoked.
+ *
* @param admin Which {@link DeviceAdmin} this request is associated with.
* @param num The number of failed password attempts at which point the
* device will wipe its data.
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index cb42236..edb40ed 100755
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -27,6 +27,7 @@
void set(int type, long triggerAtTime, in PendingIntent operation);
void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
+ void setTime(long millis);
void setTimeZone(String zone);
void remove(in PendingIntent operation);
}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index ec9f3b4..7fa5b08 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -60,6 +60,7 @@
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.ImageButton;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
@@ -105,7 +106,7 @@
// views & widgets
private TextView mBadgeLabel;
- private SearchSourceSelector mSourceSelector;
+ private ImageView mAppIcon;
private SearchAutoComplete mSearchAutoComplete;
private Button mGoButton;
private ImageButton mVoiceButton;
@@ -209,8 +210,7 @@
mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
mSearchAutoComplete = (SearchAutoComplete)
findViewById(com.android.internal.R.id.search_src_text);
- mSourceSelector = new SearchSourceSelector(
- findViewById(com.android.internal.R.id.search_source_selector));
+ mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
@@ -609,16 +609,13 @@
}
private void updateSearchAppIcon() {
- mSourceSelector.setSource(mSearchable.getSearchActivity());
- mSourceSelector.setAppSearchData(mAppSearchData);
-
// In Donut, we special-case the case of the browser to hide the app icon as if it were
// global search, for extra space for url entry.
//
// TODO: Remove this special case once the issue has been reconciled in Eclair.
if (mGlobalSearchMode || isBrowserSearch()) {
- mSourceSelector.setSourceIcon(null);
- mSourceSelector.setVisibility(View.GONE);
+ mAppIcon.setImageResource(0);
+ mAppIcon.setVisibility(View.GONE);
mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_GLOBAL,
mSearchPlate.getPaddingTop(),
mSearchPlate.getPaddingRight(),
@@ -634,8 +631,8 @@
icon = pm.getDefaultActivityIcon();
Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
}
- mSourceSelector.setSourceIcon(icon);
- mSourceSelector.setVisibility(View.VISIBLE);
+ mAppIcon.setImageDrawable(icon);
+ mAppIcon.setVisibility(View.VISIBLE);
mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL,
mSearchPlate.getPaddingTop(),
mSearchPlate.getPaddingRight(),
@@ -818,7 +815,6 @@
if (!mSearchAutoComplete.isPerformingCompletion()) {
// The user changed the query, remember it.
mUserQuery = s == null ? "" : s.toString();
- mSourceSelector.setQuery(mUserQuery);
}
}
@@ -1932,7 +1928,6 @@
query = "";
}
mUserQuery = query;
- mSourceSelector.setQuery(query);
mSearchAutoComplete.setText(query);
mSearchAutoComplete.setSelection(query.length());
}
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 12a43478..3046a2c 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1604,15 +1604,6 @@
public final static String SUGGEST_PARAMETER_LIMIT = "limit";
/**
- * Intent action for opening the search source selection activity.
- * The intent may include these extra values:
- * {@link #QUERY},
- * {@link #APP_DATA}.
- */
- public static final String INTENT_ACTION_SELECT_SEARCH_SOURCE
- = "android.intent.action.SELECT_SEARCH_SOURCE";
-
- /**
* If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
* the search dialog will switch to a different suggestion source when the
* suggestion is clicked.
diff --git a/core/java/android/app/SearchSourceSelector.java b/core/java/android/app/SearchSourceSelector.java
deleted file mode 100644
index fabf858a..0000000
--- a/core/java/android/app/SearchSourceSelector.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * 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 android.app;
-
-import com.android.internal.R;
-
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageButton;
-
-import java.util.List;
-
-/**
- * Utilities for setting up the search source selector.
- *
- * This class has two copies:
- * android.app.SearchSourceSelector
- * com.android.quicksearchbox.ui.SearchSourceSelector
- *
- * They should keep the same look and feel as much as possible,
- * but only the intent details must absolutely stay in sync.
- *
- * @hide
- */
-public class SearchSourceSelector implements View.OnClickListener {
-
- private static final String TAG = "SearchSourceSelector";
-
- // TODO: This should be defined in android.provider.Applications,
- // and have a less made-up value.
- private static final String APPLICATION_TYPE = "application/vnd.android.application";
-
- public static final int ICON_VIEW_ID = R.id.search_source_selector_icon;
-
- private final View mView;
-
- private final ImageButton mIconView;
-
- private ComponentName mSource;
-
- private Bundle mAppSearchData;
-
- private String mQuery;
-
- public SearchSourceSelector(View view) {
- mView = view;
- mIconView = (ImageButton) view.findViewById(ICON_VIEW_ID);
- mIconView.setOnClickListener(this);
- }
-
- /**
- * Sets the icon displayed in the search source selector.
- */
- public void setSourceIcon(Drawable icon) {
- mIconView.setImageDrawable(icon);
- }
-
- /**
- * Sets the current search source.
- */
- public void setSource(ComponentName source) {
- mSource = source;
- }
-
- /**
- * Sets the app-specific data that will be passed to the search activity if
- * the user opens the source selector and chooses a source.
- */
- public void setAppSearchData(Bundle appSearchData) {
- mAppSearchData = appSearchData;
- }
-
- /**
- * Sets the initial query that will be passed to the search activity if
- * the user opens the source selector and chooses a source.
- */
- public void setQuery(String query) {
- mQuery = query;
- }
-
- public void setVisibility(int visibility) {
- mView.setVisibility(visibility);
- }
-
- /**
- * Creates an intent for opening the search source selector activity.
- *
- * @param source The current search source.
- * @param query The initial query that will be passed to the search activity if
- * the user opens the source selector and chooses a source.
- * @param appSearchData The app-specific data that will be passed to the search
- * activity if the user opens the source selector and chooses a source.
- */
- public static Intent createIntent(ComponentName source, String query, Bundle appSearchData) {
- Intent intent = new Intent(SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- Uri sourceUri = componentNameToUri(source);
- if (sourceUri != null) {
- intent.setDataAndType(sourceUri, APPLICATION_TYPE);
- }
- if (query != null) {
- intent.putExtra(SearchManager.QUERY, query);
- }
- if (query != null) {
- intent.putExtra(SearchManager.APP_DATA, appSearchData);
- }
- return intent;
- }
-
- /**
- * Gets the search source from which the given
- * {@link SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE} intent was sent.
- */
- public static ComponentName getSource(Intent intent) {
- return uriToComponentName(intent.getData());
- }
-
- private static Uri componentNameToUri(ComponentName name) {
- if (name == null) return null;
- // TODO: This URI format is specificed in android.provider.Applications which is @hidden
- return new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority("applications")
- .appendEncodedPath("applications")
- .appendPath(name.getPackageName())
- .appendPath(name.getClassName())
- .build();
- }
-
- private static ComponentName uriToComponentName(Uri uri) {
- if (uri == null) return null;
- List<String> path = uri.getPathSegments();
- if (path == null || path.size() != 3) return null;
- String pkg = path.get(1);
- String cls = path.get(2);
- if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(cls)) return null;
- return new ComponentName(pkg, cls);
- }
-
- public void onClick(View v) {
- trigger();
- }
-
- private void trigger() {
- try {
- Intent intent = createIntent(mSource, mQuery, mAppSearchData);
- intent.setSourceBounds(getOnScreenRect(mIconView));
- mIconView.getContext().startActivity(intent);
- } catch (ActivityNotFoundException ex) {
- Log.e(TAG, "No source selector activity found", ex);
- }
- }
-
- // TODO: This code is replicated in lots of places:
- // - android.provider.ContactsContract.QuickContact.showQuickContact()
- // - android.widget.RemoteViews.setOnClickPendingIntent()
- // - com.android.launcher2.Launcher.onClick()
- // - com.android.launcher.Launcher.onClick()
- // - com.android.server.status.StatusBarService.Launcher.onClick()
- private static Rect getOnScreenRect(View v) {
- final float appScale = v.getResources().getCompatibilityInfo().applicationScale;
- final int[] pos = new int[2];
- v.getLocationOnScreen(pos);
- final Rect rect = new Rect();
- rect.left = (int) (pos[0] * appScale + 0.5f);
- rect.top = (int) (pos[1] * appScale + 0.5f);
- rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
- rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
- return rect;
- }
-
-}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 51d7393..72ec616 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -31,12 +31,12 @@
public class StatusBarManager {
/**
* Flag for {@link #disable} to make the status bar not expandable. Unless you also
- * set {@link #DISABLE_NOTIFICATIONS}, new notifications will continue to show.
+ * set {@link #DISABLE_NOTIFICATION_ICONS}, new notifications will continue to show.
*/
public static final int DISABLE_EXPAND = 0x00000001;
/**
- * Flag for {@link #disable} to hide notification icons and ticker text.
+ * Flag for {@link #disable} to hide notification icons and scrolling ticker text.
*/
public static final int DISABLE_NOTIFICATION_ICONS = 0x00000002;
@@ -47,6 +47,12 @@
public static final int DISABLE_NOTIFICATION_ALERTS = 0x00000004;
/**
+ * Flag for {@link #disable} to hide only the scrolling ticker. Note that
+ * {@link #DISABLE_NOTIFICATION_ICONS} implies {@link #DISABLE_NOTIFICATION_TICKER}.
+ */
+ public static final int DISABLE_NOTIFICATION_TICKER = 0x00000008;
+
+ /**
* 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/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index cb775f7..bf6c79f 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -73,6 +73,21 @@
void setBackupEnabled(boolean isEnabled);
/**
+ * Enable/disable automatic restore of application data at install time. When
+ * enabled, installation of any package will involve the Backup Manager. If data
+ * exists for the newly-installed package, either from the device's current [enabled]
+ * backup dataset or from the restore set used in the last wholesale restore operation,
+ * that data will be supplied to the new package's restore agent before the package
+ * is made generally available for launch.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param doAutoRestore When true, enables the automatic app-data restore facility. When
+ * false, this facility will be disabled.
+ */
+ void setAutoRestore(boolean doAutoRestore);
+
+ /**
* Indicate that any necessary one-time provisioning has occurred.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
diff --git a/core/java/android/backup/IRestoreSession.aidl b/core/java/android/backup/IRestoreSession.aidl
index fd40d98..bead395 100644
--- a/core/java/android/backup/IRestoreSession.aidl
+++ b/core/java/android/backup/IRestoreSession.aidl
@@ -40,6 +40,8 @@
* Restore the given set onto the device, replacing the current data of any app
* contained in the restore set with the data previously backed up.
*
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
* @return Zero on success; nonzero on error. The observer will only receive
* progress callbacks if this method returned zero.
* @param token The token from {@link getAvailableRestoreSets()} corresponding to
@@ -47,7 +49,24 @@
* @param observer If non-null, this binder points to an object that will receive
* progress callbacks during the restore operation.
*/
- int performRestore(long token, IRestoreObserver observer);
+ int restoreAll(long token, IRestoreObserver observer);
+
+ /**
+ * Restore a single application from backup. The data will be restored from the
+ * current backup dataset if the given package has stored data there, or from
+ * the dataset used during the last full device setup operation if the current
+ * backup dataset has no matching data. If no backup data exists for this package
+ * in either source, a nonzero value will be returned.
+ *
+ * @return Zero on success; nonzero on error. The observer will only receive
+ * progress callbacks if this method returned zero.
+ * @param packageName The name of the package whose data to restore. If this is
+ * not the name of the caller's own package, then the android.permission.BACKUP
+ * permission must be held.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
+ */
+ int restorePackage(in String packageName, IRestoreObserver observer);
/**
* End this restore session. After this method is called, the IRestoreSession binder
diff --git a/core/java/android/backup/RestoreSession.java b/core/java/android/backup/RestoreSession.java
index 6b35fe8..d10831e 100644
--- a/core/java/android/backup/RestoreSession.java
+++ b/core/java/android/backup/RestoreSession.java
@@ -58,25 +58,56 @@
* Restore the given set onto the device, replacing the current data of any app
* contained in the restore set with the data previously backed up.
*
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
* @return Zero on success; nonzero on error. The observer will only receive
* progress callbacks if this method returned zero.
- * @param token The token from {@link #getAvailableRestoreSets()} corresponding to
+ * @param token The token from {@link getAvailableRestoreSets()} corresponding to
* the restore set that should be used.
- * @param observer If non-null, this argument points to an object that will receive
- * progress callbacks during the restore operation. These callbacks will occur
- * on the main thread of the application.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
*/
- public int performRestore(long token, RestoreObserver observer) {
+ public int restoreAll(long token, RestoreObserver observer) {
int err = -1;
if (mObserver != null) {
- Log.d(TAG, "performRestore() called during active restore");
+ Log.d(TAG, "restoreAll() called during active restore");
return -1;
}
mObserver = new RestoreObserverWrapper(mContext, observer);
try {
- err = mBinder.performRestore(token, mObserver);
+ err = mBinder.restoreAll(token, mObserver);
} catch (RemoteException e) {
- Log.d(TAG, "Can't contact server to perform restore");
+ Log.d(TAG, "Can't contact server to restore");
+ }
+ return err;
+ }
+
+ /**
+ * Restore a single application from backup. The data will be restored from the
+ * current backup dataset if the given package has stored data there, or from
+ * the dataset used during the last full device setup operation if the current
+ * backup dataset has no matching data. If no backup data exists for this package
+ * in either source, a nonzero value will be returned.
+ *
+ * @return Zero on success; nonzero on error. The observer will only receive
+ * progress callbacks if this method returned zero.
+ * @param packageName The name of the package whose data to restore. If this is
+ * not the name of the caller's own package, then the android.permission.BACKUP
+ * permission must be held.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
+ */
+ public int restorePackage(String packageName, RestoreObserver observer) {
+ int err = -1;
+ if (mObserver != null) {
+ Log.d(TAG, "restorePackage() called during active restore");
+ return -1;
+ }
+ mObserver = new RestoreObserverWrapper(mContext, observer);
+ try {
+ err = mBinder.restorePackage(packageName, mObserver);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to restore package");
}
return err;
}
@@ -87,7 +118,7 @@
*
* <p><b>Note:</b> The caller <i>must</i> invoke this method to end the restore session,
* even if {@link #getAvailableRestoreSets()} or
- * {@link #performRestore(long, RestoreObserver)} failed.
+ * {@link #restorePackage(long, String, RestoreObserver)} failed.
*/
public void endRestoreSession() {
try {
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 0455202..c4ba05d 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -30,7 +30,7 @@
* name inside of that package.
*
*/
-public final class ComponentName implements Parcelable, Comparable<ComponentName> {
+public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
private final String mPackage;
private final String mClass;
@@ -76,6 +76,10 @@
mClass = cls.getName();
}
+ public ComponentName clone() {
+ return new ComponentName(mPackage, mClass);
+ }
+
/**
* Return the package name of this component.
*/
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index eb2d7b1..b33be23 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -61,7 +61,31 @@
*/
@Deprecated
public static final String SYNC_EXTRAS_FORCE = "force";
+
+ /**
+ * If this extra is set to true then the sync settings (like getSyncAutomatically())
+ * are ignored by the sync scheduler.
+ */
+ public static final String SYNC_EXTRAS_IGNORE_SETTINGS = "ignore_settings";
+
+ /**
+ * If this extra is set to true then any backoffs for the initial attempt (e.g. due to retries)
+ * are ignored by the sync scheduler. If this request fails and gets rescheduled then the
+ * retries will still honor the backoff.
+ */
+ public static final String SYNC_EXTRAS_IGNORE_BACKOFF = "ignore_backoff";
+
+ /**
+ * If this extra is set to true then the request will not be retried if it fails.
+ */
+ public static final String SYNC_EXTRAS_DO_NOT_RETRY = "do_not_retry";
+
+ /**
+ * Setting this extra is the equivalent of setting both {@link #SYNC_EXTRAS_IGNORE_SETTINGS}
+ * and {@link #SYNC_EXTRAS_IGNORE_BACKOFF}
+ */
public static final String SYNC_EXTRAS_MANUAL = "force";
+
public static final String SYNC_EXTRAS_UPLOAD = "upload";
public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override";
public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
@@ -966,6 +990,100 @@
}
/**
+ * Specifies that a sync should be requested with the specified the account, authority,
+ * and extras at the given frequency. If there is already another periodic sync scheduled
+ * with the account, authority and extras then a new periodic sync won't be added, instead
+ * the frequency of the previous one will be updated.
+ * <p>
+ * These periodic syncs honor the "syncAutomatically" and "masterSyncAutomatically" settings.
+ * Although these sync are scheduled at the specified frequency, it may take longer for it to
+ * actually be started if other syncs are ahead of it in the sync operation queue. This means
+ * that the actual start time may drift.
+ * <p>
+ * Periodic syncs are not allowed to have any of {@link #SYNC_EXTRAS_DO_NOT_RETRY},
+ * {@link #SYNC_EXTRAS_IGNORE_BACKOFF}, {@link #SYNC_EXTRAS_IGNORE_SETTINGS},
+ * {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE},
+ * {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true.
+ * If any are supplied then an {@link IllegalArgumentException} will be thrown.
+ *
+ * @param account the account to specify in the sync
+ * @param authority the provider to specify in the sync request
+ * @param extras extra parameters to go along with the sync request
+ * @param pollFrequency how frequently the sync should be performed, in seconds.
+ * @throws IllegalArgumentException if an illegal extra was set or if any of the parameters
+ * are null.
+ */
+ public static void addPeriodicSync(Account account, String authority, Bundle extras,
+ long pollFrequency) {
+ validateSyncExtrasBundle(extras);
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+ if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false)
+ || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false)
+ || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false)
+ || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false)
+ || extras.getBoolean(SYNC_EXTRAS_FORCE, false)
+ || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) {
+ throw new IllegalArgumentException("illegal extras were set");
+ }
+ try {
+ getContentService().addPeriodicSync(account, authority, extras, pollFrequency);
+ } catch (RemoteException e) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * Remove a periodic sync. Has no affect if account, authority and extras don't match
+ * an existing periodic sync.
+ *
+ * @param account the account of the periodic sync to remove
+ * @param authority the provider of the periodic sync to remove
+ * @param extras the extras of the periodic sync to remove
+ */
+ public static void removePeriodicSync(Account account, String authority, Bundle extras) {
+ validateSyncExtrasBundle(extras);
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+ try {
+ getContentService().removePeriodicSync(account, authority, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * Get the list of information about the periodic syncs for the given account and authority.
+ *
+ * @param account the account whose periodic syncs we are querying
+ * @param authority the provider whose periodic syncs we are querying
+ * @return a list of PeriodicSync objects. This list may be empty but will never be null.
+ */
+ public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+ try {
+ return getContentService().getPeriodicSyncs(account, authority);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Check if this account/provider is syncable.
* @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
*/
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 974a667..e0dfab5 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -32,6 +32,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
/**
* {@hide}
@@ -273,6 +275,42 @@
}
}
+ public void addPeriodicSync(Account account, String authority, Bundle extras,
+ long pollFrequency) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ long identityToken = clearCallingIdentity();
+ try {
+ getSyncManager().getSyncStorageEngine().addPeriodicSync(
+ account, authority, extras, pollFrequency);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void removePeriodicSync(Account account, String authority, Bundle extras) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ long identityToken = clearCallingIdentity();
+ try {
+ getSyncManager().getSyncStorageEngine().removePeriodicSync(account, authority, extras);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ long identityToken = clearCallingIdentity();
+ try {
+ return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+ account, providerName);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
public int getIsSyncable(Account account, String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5aefe4c..b4a0bf8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1110,7 +1110,7 @@
* @see #SENSOR_SERVICE
* @see android.hardware.SensorManager
* @see #STORAGE_SERVICE
- * @see android.storage.StorageManager
+ * @see android.os.storage.StorageManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
@@ -1243,11 +1243,11 @@
/**
* Use with {@link #getSystemService} to retrieve a {@link
- * android.storage.StorageManager} for accesssing system storage
+ * android.os.storage.StorageManager} for accesssing system storage
* functions.
*
* @see #getSystemService
- * @see android.storage.StorageManager
+ * @see android.os.storage.StorageManager
*/
public static final String STORAGE_SERVICE = "storage";
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index b0f14c1..2d906ed 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -21,6 +21,7 @@
import android.content.ISyncStatusObserver;
import android.content.SyncAdapterType;
import android.content.SyncStatusInfo;
+import android.content.PeriodicSync;
import android.net.Uri;
import android.os.Bundle;
import android.database.IContentObserver;
@@ -38,11 +39,11 @@
void requestSync(in Account account, String authority, in Bundle extras);
void cancelSync(in Account account, String authority);
-
+
/**
* Check if the provider should be synced when a network tickle is received
* @param providerName the provider whose setting we are querying
- * @return true of the provider should be synced when a network tickle is received
+ * @return true if the provider should be synced when a network tickle is received
*/
boolean getSyncAutomatically(in Account account, String providerName);
@@ -55,6 +56,33 @@
void setSyncAutomatically(in Account account, String providerName, boolean sync);
/**
+ * Get the frequency of the periodic poll, if any.
+ * @param providerName the provider whose setting we are querying
+ * @return the frequency of the periodic sync in seconds. If 0 then no periodic syncs
+ * will take place.
+ */
+ List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName);
+
+ /**
+ * Set whether or not the provider is to be synced on a periodic basis.
+ *
+ * @param providerName the provider whose behavior is being controlled
+ * @param pollFrequency the period that a sync should be performed, in seconds. If this is
+ * zero or less then no periodic syncs will be performed.
+ */
+ void addPeriodicSync(in Account account, String providerName, in Bundle extras,
+ long pollFrequency);
+
+ /**
+ * Set whether or not the provider is to be synced on a periodic basis.
+ *
+ * @param providerName the provider whose behavior is being controlled
+ * @param pollFrequency the period that a sync should be performed, in seconds. If this is
+ * zero or less then no periodic syncs will be performed.
+ */
+ void removePeriodicSync(in Account account, String providerName, in Bundle extras);
+
+ /**
* Check if this account/provider is syncable.
* @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
*/
@@ -69,15 +97,15 @@
void setMasterSyncAutomatically(boolean flag);
boolean getMasterSyncAutomatically();
-
+
/**
* Returns true if there is currently a sync operation for the given
* account or authority in the pending list, or actively being processed.
*/
boolean isSyncActive(in Account account, String authority);
-
+
ActiveSyncInfo getActiveSync();
-
+
/**
* Returns the types of the SyncAdapters that are registered with the system.
* @return Returns the types of the SyncAdapters that are registered with the system.
@@ -96,8 +124,8 @@
* Return true if the pending status is true of any matching authorities.
*/
boolean isSyncPending(in Account account, String authority);
-
+
void addStatusChangeListener(int mask, ISyncStatusObserver callback);
-
+
void removeStatusChangeListener(ISyncStatusObserver callback);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e957e20..c32999f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1381,8 +1381,8 @@
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_MEDIA_RESOURCES_AVAILABLE =
- "android.intent.action.MEDIA_RESOURCES_AVAILABILE";
+ public static final String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
/**
* Broadcast Action: Resources for a set of packages are currently
@@ -1406,8 +1406,8 @@
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_MEDIA_RESOURCES_UNAVAILABLE =
- "android.intent.action.MEDIA_RESOURCES_UNAVAILABILE";
+ public static final String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABILE";
/**
* Broadcast Action: The current system wallpaper has changed. See
@@ -2198,8 +2198,8 @@
/**
* This field is part of
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_AVAILABLE},
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_UNAVAILABLE}
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
* and contains a string array of all of the components that have changed.
* @hide
*/
@@ -2208,8 +2208,8 @@
/**
* This field is part of
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_AVAILABLE},
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_UNAVAILABLE}
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
* and contains an integer array of uids of all of the components
* that have changed.
* @hide
diff --git a/core/java/android/os/Base64Utils.java b/core/java/android/content/PeriodicSync.aidl
similarity index 62%
rename from core/java/android/os/Base64Utils.java
rename to core/java/android/content/PeriodicSync.aidl
index 684a469..4530591 100644
--- a/core/java/android/os/Base64Utils.java
+++ b/core/java/android/content/PeriodicSync.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * 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.
@@ -14,18 +14,6 @@
* limitations under the License.
*/
-package android.os;
+package android.content;
-/**
- * {@hide}
- */
-public class Base64Utils
-{
- // TODO add encode api here if possible
-
- public static byte [] decodeBase64(String data) {
- return decodeBase64Native(data);
- }
- private static native byte[] decodeBase64Native(String data);
-}
-
+parcelable PeriodicSync;
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
new file mode 100644
index 0000000..17813ec
--- /dev/null
+++ b/core/java/android/content/PeriodicSync.java
@@ -0,0 +1,84 @@
+/*
+ * 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.content;
+
+import android.os.Parcelable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.accounts.Account;
+
+/**
+ * Value type that contains information about a periodic sync. Is parcelable, making it suitable
+ * for passing in an IPC.
+ */
+public class PeriodicSync implements Parcelable {
+ /** The account to be synced */
+ public final Account account;
+ /** The authority of the sync */
+ public final String authority;
+ /** Any extras that parameters that are to be passed to the sync adapter. */
+ public final Bundle extras;
+ /** How frequently the sync should be scheduled, in seconds. */
+ public final long period;
+
+ /** Creates a new PeriodicSync, copying the Bundle */
+ public PeriodicSync(Account account, String authority, Bundle extras, long period) {
+ this.account = account;
+ this.authority = authority;
+ this.extras = new Bundle(extras);
+ this.period = period;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ account.writeToParcel(dest, flags);
+ dest.writeString(authority);
+ dest.writeBundle(extras);
+ dest.writeLong(period);
+ }
+
+ public static final Creator<PeriodicSync> CREATOR = new Creator<PeriodicSync>() {
+ public PeriodicSync createFromParcel(Parcel source) {
+ return new PeriodicSync(Account.CREATOR.createFromParcel(source),
+ source.readString(), source.readBundle(), source.readLong());
+ }
+
+ public PeriodicSync[] newArray(int size) {
+ return new PeriodicSync[size];
+ }
+ };
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof PeriodicSync)) {
+ return false;
+ }
+
+ final PeriodicSync other = (PeriodicSync) o;
+
+ return account.equals(other.account)
+ && authority.equals(other.authority)
+ && period == other.period
+ && SyncStorageEngine.equals(extras, other.extras);
+ }
+}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 699b61d..ebb95e87 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -52,14 +52,7 @@
import android.util.Log;
import android.util.Pair;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -74,12 +67,6 @@
public class SyncManager implements OnAccountsUpdateListener {
private static final String TAG = "SyncManager";
- // used during dumping of the Sync history
- private static final long MILLIS_IN_HOUR = 1000 * 60 * 60;
- private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24;
- private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
- private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4;
-
/** Delay a sync due to local changes this long. In milliseconds */
private static final long LOCAL_SYNC_DELAY;
@@ -137,7 +124,7 @@
private Context mContext;
- private volatile Account[] mAccounts = null;
+ private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
volatile private PowerManager.WakeLock mSyncWakeLock;
volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
@@ -157,9 +144,7 @@
// set if the sync active indicator should be reported
private boolean mNeedSyncActiveNotification = false;
- private volatile boolean mSyncPollInitialized;
private final PendingIntent mSyncAlarmIntent;
- private final PendingIntent mSyncPollAlarmIntent;
// Synchronized on "this". Instead of using this directly one should instead call
// its accessor, getConnManager().
private ConnectivityManager mConnManagerDoNotUseDirectly;
@@ -201,9 +186,11 @@
}
};
+ private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
+
public void onAccountsUpdated(Account[] accounts) {
// remember if this was the first time this was called after an update
- final boolean justBootedUp = mAccounts == null;
+ final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
mAccounts = accounts;
// if a sync is in progress yet it is no longer in the accounts list,
@@ -276,7 +263,6 @@
// ignore the rest of the states -- leave our boolean alone.
}
if (mDataConnectionIsConnected) {
- initializeSyncPoll();
sendCheckAlarmsMessage();
}
}
@@ -291,14 +277,8 @@
};
private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
- private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM";
private final SyncHandler mSyncHandler;
- private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours
- private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours
-
- private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
-
private volatile boolean mBootCompleted = false;
private ConnectivityManager getConnectivityManager() {
@@ -338,9 +318,6 @@
mSyncAlarmIntent = PendingIntent.getBroadcast(
mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
- mSyncPollAlarmIntent = PendingIntent.getBroadcast(
- mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0);
-
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
@@ -396,49 +373,6 @@
}
}
- private synchronized void initializeSyncPoll() {
- if (mSyncPollInitialized) return;
- mSyncPollInitialized = true;
-
- mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM));
-
- // load the next poll time from shared preferences
- long absoluteAlarmTime = readSyncPollTime();
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime);
- }
-
- // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then
- // schedule the poll immediately, if it is too far in the future then cap it at
- // MAX_SYNC_POLL_DELAY_SECONDS.
- long absoluteNow = System.currentTimeMillis();
- long relativeNow = SystemClock.elapsedRealtime();
- long relativeAlarmTime = relativeNow;
- if (absoluteAlarmTime > absoluteNow) {
- long delayInMs = absoluteAlarmTime - absoluteNow;
- final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
- if (delayInMs > maxDelayInMs) {
- delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
- }
- relativeAlarmTime += delayInMs;
- }
-
- // schedule an alarm for the next poll time
- scheduleSyncPollAlarm(relativeAlarmTime);
- }
-
- private void scheduleSyncPollAlarm(long relativeAlarmTime) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime
- + ", now is " + SystemClock.elapsedRealtime()
- + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime()));
- }
- ensureAlarmService();
- mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime,
- mSyncPollAlarmIntent);
- }
-
/**
* Return a random value v that satisfies minValue <= v < maxValue. The difference between
* maxValue and minValue must be less than Integer.MAX_VALUE.
@@ -453,68 +387,6 @@
return minValue + random.nextInt((int)spread);
}
- private void handleSyncPollAlarm() {
- // determine the next poll time
- long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000;
- long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs;
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs);
-
- // write the absolute time to shared preferences
- writeSyncPollTime(System.currentTimeMillis() + delayMs);
-
- // schedule an alarm for the next poll time
- scheduleSyncPollAlarm(nextRelativePollTimeMs);
-
- // perform a poll
- scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */,
- new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */);
- }
-
- private void writeSyncPollTime(long when) {
- File f = new File(SYNCMANAGER_PREFS_FILENAME);
- DataOutputStream str = null;
- try {
- str = new DataOutputStream(new FileOutputStream(f));
- str.writeLong(when);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "error writing to file " + f, e);
- } catch (IOException e) {
- Log.w(TAG, "error writing to file " + f, e);
- } finally {
- if (str != null) {
- try {
- str.close();
- } catch (IOException e) {
- Log.w(TAG, "error closing file " + f, e);
- }
- }
- }
- }
-
- private long readSyncPollTime() {
- File f = new File(SYNCMANAGER_PREFS_FILENAME);
-
- DataInputStream str = null;
- try {
- str = new DataInputStream(new FileInputStream(f));
- return str.readLong();
- } catch (FileNotFoundException e) {
- writeSyncPollTime(0);
- } catch (IOException e) {
- Log.w(TAG, "error reading file " + f, e);
- } finally {
- if (str != null) {
- try {
- str.close();
- } catch (IOException e) {
- Log.w(TAG, "error closing file " + f, e);
- }
- }
- }
- return 0;
- }
-
public ActiveSyncContext getActiveSyncContext() {
return mActiveSyncContext;
}
@@ -616,11 +488,6 @@
Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (mAccounts == null) {
- Log.e(TAG, "scheduleSync: the accounts aren't known yet, this should never happen");
- return;
- }
-
if (!isSyncEnabled()) {
if (isLoggable) {
Log.v(TAG, "not syncing because sync is disabled");
@@ -645,13 +512,6 @@
// if the accounts aren't configured yet then we can't support an account-less
// sync request
accounts = mAccounts;
- if (accounts == null) {
- // not ready yet
- if (isLoggable) {
- Log.v(TAG, "scheduleSync: no accounts yet, dropping");
- }
- return;
- }
if (accounts.length == 0) {
if (isLoggable) {
Log.v(TAG, "scheduleSync: no accounts configured, dropping");
@@ -662,6 +522,12 @@
final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+ if (manualSync) {
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+ final boolean ignoreSettings =
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
int source;
if (uploadOnly) {
@@ -720,7 +586,7 @@
final boolean syncAutomatically = masterSyncAutomatically
&& mSyncStorageEngine.getSyncAutomatically(account, authority);
boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
+ ignoreSettings || (backgroundDataUsageAllowed && syncAutomatically);
if (!syncAllowed) {
if (isLoggable) {
Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
@@ -799,12 +665,6 @@
}
}
- class SyncPollAlarmReceiver extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- handleSyncPollAlarm();
- }
- }
-
private void clearBackoffSetting(SyncOperation op) {
mSyncStorageEngine.setBackoff(op.account, op.authority,
SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
@@ -923,7 +783,7 @@
mSyncStorageEngine.setBackoff(account, authority,
SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
synchronized (mSyncQueue) {
- mSyncQueue.clear(account, authority);
+ mSyncQueue.remove(account, authority);
}
}
@@ -933,21 +793,29 @@
Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
}
+ operation = new SyncOperation(operation);
+
+ // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
+ // request. Retries of the request will always honor the backoff, so clear the
+ // flag in case we retry this request.
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
+ operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+ }
+
// If this sync aborted because the internal sync loop retried too many times then
// don't reschedule. Otherwise we risk getting into a retry loop.
// If the operation succeeded to some extent then retry immediately.
// If this was a two-way sync then retry soft errors with an exponential backoff.
// If this was an upward sync then schedule a two-way sync immediately.
// Otherwise do not reschedule.
- if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
- Log.d(TAG, "not retrying sync operation because it is a manual sync: "
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
+ Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
+ operation);
} else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
- final SyncOperation newSyncOperation = new SyncOperation(operation);
- newSyncOperation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ "encountered an error: " + operation);
- scheduleSyncOperation(newSyncOperation);
+ scheduleSyncOperation(operation);
} else if (syncResult.tooManyRetries) {
Log.d(TAG, "not retrying sync operation because it retried too many times: "
+ operation);
@@ -956,13 +824,13 @@
Log.d(TAG, "retrying sync operation because even though it had an error "
+ "it achieved some success");
}
- scheduleSyncOperation(new SyncOperation(operation));
+ scheduleSyncOperation(operation);
} else if (syncResult.hasSoftError()) {
if (isLoggable) {
Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+ operation);
}
- scheduleSyncOperation(new SyncOperation(operation));
+ scheduleSyncOperation(operation);
} else {
Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+ operation);
@@ -1078,13 +946,14 @@
final Account[] accounts = mAccounts;
pw.print("accounts: ");
- if (accounts != null) {
+ if (accounts != INITIAL_ACCOUNTS_ARRAY) {
pw.println(accounts.length);
} else {
- pw.println("none");
+ pw.println("not known yet");
}
final long now = SystemClock.elapsedRealtime();
- pw.print("now: "); pw.println(now);
+ pw.print("now: "); pw.print(now);
+ pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
pw.println(" (HH:MM:SS)");
pw.print("time spent syncing: ");
@@ -1102,7 +971,9 @@
pw.println("no alarm is scheduled (there had better not be any pending syncs)");
}
- pw.print("active sync: "); pw.println(mActiveSyncContext);
+ final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
+
+ pw.print("active sync: "); pw.println(activeSyncContext);
pw.print("notification info: ");
sb.setLength(0);
@@ -1125,6 +996,11 @@
pw.print(authority != null ? authority.account : "<no account>");
pw.print(" ");
pw.print(authority != null ? authority.authority : "<no account>");
+ if (activeSyncContext != null) {
+ pw.print(" ");
+ pw.print(SyncStorageEngine.SOURCES[
+ activeSyncContext.mSyncOperation.syncSource]);
+ }
pw.print(", duration is ");
pw.println(DateUtils.formatElapsedTime(durationInSeconds));
} else {
@@ -1152,80 +1028,76 @@
}
}
- HashSet<Account> processedAccounts = new HashSet<Account>();
- ArrayList<SyncStatusInfo> statuses
- = mSyncStorageEngine.getSyncStatus();
- if (statuses != null && statuses.size() > 0) {
- pw.println();
- pw.println("Sync Status");
- final int N = statuses.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo status = statuses.get(i);
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(status.authorityId);
- if (authority != null) {
- Account curAccount = authority.account;
+ // join the installed sync adapter with the accounts list and emit for everything
+ pw.println();
+ pw.println("Sync Status");
+ for (Account account : accounts) {
+ pw.print(" Account "); pw.print(account.name);
+ pw.print(" "); pw.print(account.type);
+ pw.println(":");
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType :
+ mSyncAdapters.getAllServices()) {
+ if (!syncAdapterType.type.accountType.equals(account.type)) {
+ continue;
+ }
- if (processedAccounts.contains(curAccount)) {
- continue;
- }
-
- processedAccounts.add(curAccount);
-
- pw.print(" Account "); pw.print(authority.account.name);
- pw.print(" "); pw.print(authority.account.type);
- pw.println(":");
- for (int j=i; j<N; j++) {
- status = statuses.get(j);
- authority = mSyncStorageEngine.getAuthority(status.authorityId);
- if (!curAccount.equals(authority.account)) {
- continue;
- }
- pw.print(" "); pw.print(authority.authority);
- pw.println(":");
- final String syncable = authority.syncable > 0
- ? "syncable"
- : (authority.syncable == 0 ? "not syncable" : "not initialized");
- final String enabled = authority.enabled ? "enabled" : "disabled";
- final String delayUntil = authority.delayUntil > now
- ? "delay for " + ((authority.delayUntil - now) / 1000) + " sec"
- : "no delay required";
- final String backoff = authority.backoffTime > now
- ? "backoff for " + ((authority.backoffTime - now) / 1000)
- + " sec"
- : "no backoff required";
- final String backoffDelay = authority.backoffDelay > 0
- ? ("the backoff increment is " + authority.backoffDelay / 1000
- + " sec")
- : "no backoff increment";
- pw.println(String.format(
- " settings: %s, %s, %s, %s, %s",
- enabled, syncable, backoff, backoffDelay, delayUntil));
- pw.print(" count: local="); pw.print(status.numSourceLocal);
- pw.print(" poll="); pw.print(status.numSourcePoll);
- pw.print(" server="); pw.print(status.numSourceServer);
- pw.print(" user="); pw.print(status.numSourceUser);
- pw.print(" total="); pw.println(status.numSyncs);
- pw.print(" total duration: ");
- pw.println(DateUtils.formatElapsedTime(
- status.totalElapsedTime/1000));
- if (status.lastSuccessTime != 0) {
- pw.print(" SUCCESS: source=");
- pw.print(SyncStorageEngine.SOURCES[
- status.lastSuccessSource]);
- pw.print(" time=");
- pw.println(formatTime(status.lastSuccessTime));
- } else {
- pw.print(" FAILURE: source=");
- pw.print(SyncStorageEngine.SOURCES[
- status.lastFailureSource]);
- pw.print(" initialTime=");
- pw.print(formatTime(status.initialFailureTime));
- pw.print(" lastTime=");
- pw.println(formatTime(status.lastFailureTime));
- pw.print(" message: "); pw.println(status.lastFailureMesg);
- }
- }
+ SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getAuthority(
+ account, syncAdapterType.type.authority);
+ SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
+ pw.print(" "); pw.print(settings.authority);
+ pw.println(":");
+ pw.print(" settings:");
+ pw.print(" " + (settings.syncable > 0
+ ? "syncable"
+ : (settings.syncable == 0 ? "not syncable" : "not initialized")));
+ pw.print(", " + (settings.enabled ? "enabled" : "disabled"));
+ if (settings.delayUntil > now) {
+ pw.print(", delay for "
+ + ((settings.delayUntil - now) / 1000) + " sec");
+ }
+ if (settings.backoffTime > now) {
+ pw.print(", backoff for "
+ + ((settings.backoffTime - now) / 1000) + " sec");
+ }
+ if (settings.backoffDelay > 0) {
+ pw.print(", the backoff increment is " + settings.backoffDelay / 1000
+ + " sec");
+ }
+ pw.println();
+ for (int periodicIndex = 0;
+ periodicIndex < settings.periodicSyncs.size();
+ periodicIndex++) {
+ Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex);
+ long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex);
+ long nextPeriodicTime = lastPeriodicTime + info.second * 1000;
+ pw.println(" periodic period=" + info.second
+ + ", extras=" + info.first
+ + ", next=" + formatTime(nextPeriodicTime));
+ }
+ pw.print(" count: local="); pw.print(status.numSourceLocal);
+ pw.print(" poll="); pw.print(status.numSourcePoll);
+ pw.print(" periodic="); pw.print(status.numSourcePeriodic);
+ pw.print(" server="); pw.print(status.numSourceServer);
+ pw.print(" user="); pw.print(status.numSourceUser);
+ pw.print(" total="); pw.print(status.numSyncs);
+ pw.println();
+ pw.print(" total duration: ");
+ pw.println(DateUtils.formatElapsedTime(status.totalElapsedTime/1000));
+ if (status.lastSuccessTime != 0) {
+ pw.print(" SUCCESS: source=");
+ pw.print(SyncStorageEngine.SOURCES[status.lastSuccessSource]);
+ pw.print(" time=");
+ pw.println(formatTime(status.lastSuccessTime));
+ }
+ if (status.lastFailureTime != 0) {
+ pw.print(" FAILURE: source=");
+ pw.print(SyncStorageEngine.SOURCES[
+ status.lastFailureSource]);
+ pw.print(" initialTime=");
+ pw.print(formatTime(status.initialFailureTime));
+ pw.print(" lastTime=");
+ pw.println(formatTime(status.lastFailureTime));
+ pw.print(" message: "); pw.println(status.lastFailureMesg);
}
}
}
@@ -1580,6 +1452,36 @@
}
}
+ private boolean isSyncAllowed(Account account, String authority, boolean ignoreSettings,
+ boolean backgroundDataUsageAllowed) {
+ Account[] accounts = mAccounts;
+
+ // Sync is disabled, drop this operation.
+ if (!isSyncEnabled()) {
+ return false;
+ }
+
+ // skip the sync if the account of this operation no longer exists
+ if (!ArrayUtils.contains(accounts, account)) {
+ return false;
+ }
+
+ // skip the sync if it isn't manual and auto sync is disabled
+ final boolean syncAutomatically =
+ mSyncStorageEngine.getSyncAutomatically(account, authority)
+ && mSyncStorageEngine.getMasterSyncAutomatically();
+ if (!(ignoreSettings || (backgroundDataUsageAllowed && syncAutomatically))) {
+ return false;
+ }
+
+ if (mSyncStorageEngine.getIsSyncable(account, authority) <= 0) {
+ // if not syncable or if the syncable is unknown (< 0), don't allow
+ return false;
+ }
+
+ return true;
+ }
+
private void runStateSyncing() {
// if the sync timeout has been reached then cancel it
@@ -1589,7 +1491,7 @@
if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
SyncOperation nextSyncOperation;
synchronized (mSyncQueue) {
- nextSyncOperation = mSyncQueue.nextReadyToRun(now);
+ nextSyncOperation = getNextReadyToRunSyncOperation(now);
}
if (nextSyncOperation != null) {
Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
@@ -1627,7 +1529,7 @@
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
// when the account lookup request does complete.
Account[] accounts = mAccounts;
- if (accounts == null) {
+ if (accounts == INITIAL_ACCOUNTS_ARRAY) {
if (isLoggable) {
Log.v(TAG, "runStateIdle: accounts not known, skipping");
}
@@ -1643,7 +1545,7 @@
synchronized (mSyncQueue) {
final long now = SystemClock.elapsedRealtime();
while (true) {
- op = mSyncQueue.nextReadyToRun(now);
+ op = getNextReadyToRunSyncOperation(now);
if (op == null) {
if (isLoggable) {
Log.v(TAG, "runStateIdle: no more sync operations, returning");
@@ -1655,42 +1557,10 @@
// from the queue now
mSyncQueue.remove(op);
- // Sync is disabled, drop this operation.
- if (!isSyncEnabled()) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync disabled, dropping " + op);
- }
- continue;
- }
-
- // skip the sync if the account of this operation no longer exists
- if (!ArrayUtils.contains(accounts, op.account)) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: account not present, dropping " + op);
- }
- continue;
- }
-
- // skip the sync if it isn't manual and auto sync is disabled
- final boolean manualSync =
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- final boolean syncAutomatically =
- mSyncStorageEngine.getSyncAutomatically(op.account, op.authority)
- && mSyncStorageEngine.getMasterSyncAutomatically();
- if (!(manualSync || (backgroundDataUsageAllowed && syncAutomatically))) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
- + "dropping " + op);
- }
- continue;
- }
-
- if (mSyncStorageEngine.getIsSyncable(op.account, op.authority) <= 0) {
- // if not syncable or if the syncable is unknown (< 0), don't allow
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
- + "dropping " + op);
- }
+ final boolean ignoreSettings = op.extras
+ .getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+ if (!isSyncAllowed(op.account, op.authority, ignoreSettings,
+ backgroundDataUsageAllowed)) {
continue;
}
@@ -1736,6 +1606,74 @@
// MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
}
+ private SyncOperation getNextPeriodicSyncOperation() {
+ final boolean backgroundDataUsageAllowed =
+ getConnectivityManager().getBackgroundDataSetting();
+ SyncStorageEngine.AuthorityInfo best = null;
+ long bestPollTimeAbsolute = Long.MAX_VALUE;
+ Bundle bestExtras = null;
+ ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
+ for (SyncStorageEngine.AuthorityInfo info : infos) {
+ if (!isSyncAllowed(info.account, info.authority, false /* ignoreSettings */,
+ backgroundDataUsageAllowed)) {
+ continue;
+ }
+ SyncStatusInfo status = mSyncStorageEngine.getStatusByAccountAndAuthority(
+ info.account, info.authority);
+ int i = 0;
+ for (Pair<Bundle, Long> periodicSync : info.periodicSyncs) {
+ long lastPollTimeAbsolute = status != null ? status.getPeriodicSyncTime(i) : 0;
+ final Bundle extras = periodicSync.first;
+ final Long periodInSeconds = periodicSync.second;
+ long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
+ if (nextPollTimeAbsolute < bestPollTimeAbsolute) {
+ best = info;
+ bestPollTimeAbsolute = nextPollTimeAbsolute;
+ bestExtras = extras;
+ }
+ i++;
+ }
+ }
+
+ if (best == null) {
+ return null;
+ }
+
+ final long nowAbsolute = System.currentTimeMillis();
+ final SyncOperation syncOperation = new SyncOperation(best.account,
+ SyncStorageEngine.SOURCE_PERIODIC,
+ best.authority, bestExtras, 0 /* delay */);
+ syncOperation.earliestRunTime = SystemClock.elapsedRealtime()
+ + (bestPollTimeAbsolute - nowAbsolute);
+ if (syncOperation.earliestRunTime < 0) {
+ syncOperation.earliestRunTime = 0;
+ }
+ return syncOperation;
+ }
+
+ public Pair<SyncOperation, Long> bestSyncOperationCandidate() {
+ Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
+ SyncOperation nextOp = nextOpAndRunTime != null ? nextOpAndRunTime.first : null;
+ Long nextRunTime = nextOpAndRunTime != null ? nextOpAndRunTime.second : null;
+ SyncOperation pollOp = getNextPeriodicSyncOperation();
+ if (nextOp != null
+ && (pollOp == null || nextOp.expedited
+ || nextRunTime <= pollOp.earliestRunTime)) {
+ return nextOpAndRunTime;
+ } else if (pollOp != null) {
+ return Pair.create(pollOp, pollOp.earliestRunTime);
+ } else {
+ return null;
+ }
+ }
+
+ private SyncOperation getNextReadyToRunSyncOperation(long now) {
+ Pair<SyncOperation, Long> nextOpAndRunTime = bestSyncOperationCandidate();
+ return nextOpAndRunTime != null && nextOpAndRunTime.second <= now
+ ? nextOpAndRunTime.first
+ : null;
+ }
+
private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
mActiveSyncContext.mSyncAdapter = syncAdapter;
final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
@@ -1948,7 +1886,6 @@
// in each of these cases the sync loop will be kicked, which will cause this
// method to be called again
if (!mDataConnectionIsConnected) return;
- if (mAccounts == null) return;
if (mStorageIsLow) return;
final long now = SystemClock.elapsedRealtime();
@@ -1961,7 +1898,8 @@
ActiveSyncContext activeSyncContext = mActiveSyncContext;
if (activeSyncContext == null) {
synchronized (mSyncQueue) {
- alarmTime = mSyncQueue.nextRunTime(now);
+ Pair<SyncOperation, Long> candidate = bestSyncOperationCandidate();
+ alarmTime = candidate != null ? candidate.second : null;
}
} else {
final long notificationTime =
@@ -2102,9 +2040,8 @@
SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
syncOperation.account.name.hashCode());
- mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
- downstreamActivity, upstreamActivity);
+ mSyncStorageEngine.stopSyncEvent(rowId, syncOperation.extras, elapsedTime,
+ resultMessage, downstreamActivity, upstreamActivity);
}
}
-
}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index 2d6e833..4599165 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -26,6 +26,9 @@
this.extras = new Bundle(extras);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index a9f15d9..bb21488 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -2,8 +2,6 @@
import com.google.android.collect.Maps;
-import android.os.Bundle;
-import android.os.SystemClock;
import android.util.Pair;
import android.util.Log;
import android.accounts.Account;
@@ -32,10 +30,9 @@
final int N = ops.size();
for (int i=0; i<N; i++) {
SyncStorageEngine.PendingOperation op = ops.get(i);
- // -1 is a special value that means expedited
- final int delay = op.expedited ? -1 : 0;
SyncOperation syncOperation = new SyncOperation(
- op.account, op.syncSource, op.authority, op.extras, delay);
+ op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+ syncOperation.expedited = op.expedited;
syncOperation.pendingOperation = op;
add(syncOperation, op);
}
@@ -90,8 +87,15 @@
return true;
}
+ /**
+ * Remove the specified operation if it is in the queue.
+ * @param operation the operation to remove
+ */
public void remove(SyncOperation operation) {
SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+ if (operationToRemove == null) {
+ return;
+ }
if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
final String errorMessage = "unable to find pending row for " + operationToRemove;
Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
@@ -102,54 +106,34 @@
* Find the operation that should run next. Operations are sorted by their earliestRunTime,
* prioritizing expedited operations. The earliestRunTime is adjusted by the sync adapter's
* backoff and delayUntil times, if any.
- * @param now the current {@link android.os.SystemClock#elapsedRealtime()}
* @return the operation that should run next and when it should run. The time may be in
* the future. It is expressed in milliseconds since boot.
*/
- private Pair<SyncOperation, Long> nextOperation(long now) {
- SyncOperation lowestOp = null;
- long lowestOpRunTime = 0;
+ public Pair<SyncOperation, Long> nextOperation() {
+ SyncOperation best = null;
+ long bestRunTime = 0;
for (SyncOperation op : mOperationsMap.values()) {
- // effectiveRunTime:
- // - backoffTime > currentTime : backoffTime
- // - backoffTime <= currentTime : op.runTime
- Pair<Long, Long> backoff = null;
- long delayUntilTime = 0;
- final boolean isManualSync =
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- if (!isManualSync) {
- backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
- delayUntilTime = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
+ long opRunTime = op.earliestRunTime;
+ if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
+ Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
+ long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
+ opRunTime = Math.max(
+ Math.max(opRunTime, delayUntil),
+ backoff != null ? backoff.first : 0);
}
- long backoffTime = Math.max(backoff != null ? backoff.first : 0, delayUntilTime);
- long opRunTime = backoffTime > now ? backoffTime : op.earliestRunTime;
- if (lowestOp == null
- || (lowestOp.expedited == op.expedited
- ? opRunTime < lowestOpRunTime
- : op.expedited)) {
- lowestOp = op;
- lowestOpRunTime = opRunTime;
+ // if the expedited state of both ops are the same then compare their runtime.
+ // Otherwise the candidate is only better than the current best if the candidate
+ // is expedited.
+ if (best == null
+ || (best.expedited == op.expedited ? opRunTime < bestRunTime : op.expedited)) {
+ best = op;
+ bestRunTime = opRunTime;
}
}
- if (lowestOp == null) {
+ if (best == null) {
return null;
}
- return Pair.create(lowestOp, lowestOpRunTime);
- }
-
- /**
- * Return when the next SyncOperation will be ready to run or null if there are
- * none.
- * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
- * decide if the sync operation is ready to run
- * @return when the next SyncOperation will be ready to run, expressed in elapsedRealtime()
- */
- public Long nextRunTime(long now) {
- Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
- if (nextOpAndRunTime == null) {
- return null;
- }
- return nextOpAndRunTime.second;
+ return Pair.create(best, bestRunTime);
}
/**
@@ -158,21 +142,25 @@
* decide if the sync operation is ready to run
* @return the SyncOperation that should be run next and is ready to run.
*/
- public SyncOperation nextReadyToRun(long now) {
- Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
+ public Pair<SyncOperation, Long> nextReadyToRun(long now) {
+ Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
return null;
}
- return nextOpAndRunTime.first;
+ return nextOpAndRunTime;
}
- public void clear(Account account, String authority) {
+ public void remove(Account account, String authority) {
Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, SyncOperation> entry = entries.next();
SyncOperation syncOperation = entry.getValue();
- if (account != null && !syncOperation.account.equals(account)) continue;
- if (authority != null && !syncOperation.authority.equals(authority)) continue;
+ if (account != null && !syncOperation.account.equals(account)) {
+ continue;
+ }
+ if (authority != null && !syncOperation.authority.equals(authority)) {
+ continue;
+ }
entries.remove();
if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
final String errorMessage = "unable to find pending row for " + syncOperation;
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index b8fda03..bb2b2da 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -20,10 +20,12 @@
import android.os.Parcelable;
import android.util.Log;
+import java.util.ArrayList;
+
/** @hide */
public class SyncStatusInfo implements Parcelable {
- static final int VERSION = 1;
-
+ static final int VERSION = 2;
+
public final int authorityId;
public long totalElapsedTime;
public int numSyncs;
@@ -31,6 +33,7 @@
public int numSourceServer;
public int numSourceLocal;
public int numSourceUser;
+ public int numSourcePeriodic;
public long lastSuccessTime;
public int lastSuccessSource;
public long lastFailureTime;
@@ -39,7 +42,10 @@
public long initialFailureTime;
public boolean pending;
public boolean initialize;
-
+ public ArrayList<Long> periodicSyncTimes;
+
+ private static final String TAG = "Sync";
+
SyncStatusInfo(int authorityId) {
this.authorityId = authorityId;
}
@@ -50,10 +56,11 @@
return Integer.parseInt(lastFailureMesg);
}
} catch (NumberFormatException e) {
+ Log.d(TAG, "error parsing lastFailureMesg of " + lastFailureMesg, e);
}
return def;
}
-
+
public int describeContents() {
return 0;
}
@@ -75,11 +82,19 @@
parcel.writeLong(initialFailureTime);
parcel.writeInt(pending ? 1 : 0);
parcel.writeInt(initialize ? 1 : 0);
+ if (periodicSyncTimes != null) {
+ parcel.writeInt(periodicSyncTimes.size());
+ for (long periodicSyncTime : periodicSyncTimes) {
+ parcel.writeLong(periodicSyncTime);
+ }
+ } else {
+ parcel.writeInt(-1);
+ }
}
SyncStatusInfo(Parcel parcel) {
int version = parcel.readInt();
- if (version != VERSION) {
+ if (version != VERSION && version != 1) {
Log.w("SyncStatusInfo", "Unknown version: " + version);
}
authorityId = parcel.readInt();
@@ -97,8 +112,51 @@
initialFailureTime = parcel.readLong();
pending = parcel.readInt() != 0;
initialize = parcel.readInt() != 0;
+ if (version == 1) {
+ periodicSyncTimes = null;
+ } else {
+ int N = parcel.readInt();
+ if (N < 0) {
+ periodicSyncTimes = null;
+ } else {
+ periodicSyncTimes = new ArrayList<Long>();
+ for (int i=0; i<N; i++) {
+ periodicSyncTimes.add(parcel.readLong());
+ }
+ }
+ }
}
-
+
+ public void setPeriodicSyncTime(int index, long when) {
+ ensurePeriodicSyncTimeSize(index);
+ periodicSyncTimes.set(index, when);
+ }
+
+ private void ensurePeriodicSyncTimeSize(int index) {
+ if (periodicSyncTimes == null) {
+ periodicSyncTimes = new ArrayList<Long>(0);
+ }
+
+ final int requiredSize = index + 1;
+ if (periodicSyncTimes.size() < requiredSize) {
+ for (int i = periodicSyncTimes.size(); i < requiredSize; i++) {
+ periodicSyncTimes.add((long) 0);
+ }
+ }
+ }
+
+ public long getPeriodicSyncTime(int index) {
+ if (periodicSyncTimes == null || periodicSyncTimes.size() < (index + 1)) {
+ return 0;
+ }
+ return periodicSyncTimes.get(index);
+ }
+
+ public void removePeriodicSyncTime(int index) {
+ ensurePeriodicSyncTimeSize(index);
+ periodicSyncTimes.remove(index);
+ }
+
public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
public SyncStatusInfo createFromParcel(Parcel in) {
return new SyncStatusInfo(in);
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index db70096..07a1f46 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -36,7 +36,6 @@
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -50,6 +49,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.TimeZone;
+import java.util.List;
/**
* Singleton that tracks the sync data and overall sync
@@ -62,6 +62,8 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_FILE = false;
+ private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+
// @VisibleForTesting
static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
@@ -89,6 +91,9 @@
/** Enum value for a user-initiated sync. */
public static final int SOURCE_USER = 3;
+ /** Enum value for a periodic sync. */
+ public static final int SOURCE_PERIODIC = 4;
+
public static final long NOT_IN_BACKOFF_MODE = -1;
private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
@@ -99,7 +104,8 @@
public static final String[] SOURCES = { "SERVER",
"LOCAL",
"POLL",
- "USER" };
+ "USER",
+ "PERIODIC" };
// The MESG column will contain one of these or one of the Error types.
public static final String MESG_SUCCESS = "success";
@@ -164,6 +170,7 @@
long backoffTime;
long backoffDelay;
long delayUntil;
+ final ArrayList<Pair<Bundle, Long>> periodicSyncs;
AuthorityInfo(Account account, String authority, int ident) {
this.account = account;
@@ -173,6 +180,8 @@
syncable = -1; // default to "unknown"
backoffTime = -1; // if < 0 then we aren't in backoff mode
backoffDelay = -1; // if < 0 then we aren't in backoff mode
+ periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
+ periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
}
}
@@ -228,6 +237,7 @@
private int mYearInDays;
private final Context mContext;
+
private static volatile SyncStorageEngine sSyncStorageEngine = null;
/**
@@ -262,17 +272,15 @@
private int mNextHistoryId = 0;
private boolean mMasterSyncAutomatically = true;
- private SyncStorageEngine(Context context) {
+ private SyncStorageEngine(Context context, File dataDir) {
mContext = context;
sSyncStorageEngine = this;
mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
- // This call will return the correct directory whether Encrypted File Systems is
- // enabled or not.
- File dataDir = Environment.getSecureDataDirectory();
File systemDir = new File(dataDir, "system");
File syncDir = new File(systemDir, "sync");
+ syncDir.mkdirs();
mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
@@ -286,14 +294,17 @@
}
public static SyncStorageEngine newTestInstance(Context context) {
- return new SyncStorageEngine(context);
+ return new SyncStorageEngine(context, context.getFilesDir());
}
public static void init(Context context) {
if (sSyncStorageEngine != null) {
return;
}
- sSyncStorageEngine = new SyncStorageEngine(context);
+ // This call will return the correct directory whether Encrypted File Systems is
+ // enabled or not.
+ File dataDir = Environment.getSecureDataDirectory();
+ sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
}
public static SyncStorageEngine getSingleton() {
@@ -475,7 +486,7 @@
}
} else {
AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, providerName, -1, false);
+ getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true);
if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
return;
}
@@ -483,9 +494,6 @@
authority.backoffDelay = nextDelay;
changed = true;
}
- if (changed) {
- writeAccountInfoLocked();
- }
}
if (changed) {
@@ -499,12 +507,12 @@
+ " -> delayUntil " + delayUntil);
}
synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ account, providerName, -1 /* ident */, true);
if (authority.delayUntil == delayUntil) {
return;
}
authority.delayUntil = delayUntil;
- writeAccountInfoLocked();
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -520,6 +528,90 @@
}
}
+ private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras,
+ long period, boolean add) {
+ if (period <= 0) {
+ period = 0;
+ }
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName
+ + " -> period " + period + ", extras " + extras);
+ }
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+ if (add) {
+ boolean alreadyPresent = false;
+ for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+ Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
+ final Bundle existingExtras = syncInfo.first;
+ if (equals(existingExtras, extras)) {
+ if (syncInfo.second == period) {
+ return;
+ }
+ authority.periodicSyncs.set(i, Pair.create(extras, period));
+ alreadyPresent = true;
+ break;
+ }
+ }
+ if (!alreadyPresent) {
+ authority.periodicSyncs.add(Pair.create(extras, period));
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
+ }
+ } else {
+ SyncStatusInfo status = mSyncStatus.get(authority.ident);
+ boolean changed = false;
+ Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
+ int i = 0;
+ while (iterator.hasNext()) {
+ Pair<Bundle, Long> syncInfo = iterator.next();
+ if (equals(syncInfo.first, extras)) {
+ iterator.remove();
+ changed = true;
+ if (status != null) {
+ status.removePeriodicSyncTime(i);
+ }
+ } else {
+ i++;
+ }
+ }
+ if (!changed) {
+ return;
+ }
+ }
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public void addPeriodicSync(Account account, String providerName, Bundle extras,
+ long pollFrequency) {
+ updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */);
+ }
+
+ public void removePeriodicSync(Account account, String providerName, Bundle extras) {
+ updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */,
+ false /* remove */);
+ }
+
+ public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+ ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs");
+ if (authority != null) {
+ for (Pair<Bundle, Long> item : authority.periodicSyncs) {
+ syncs.add(new PeriodicSync(account, providerName, item.first, item.second));
+ }
+ }
+ }
+ return syncs;
+ }
+
public void setMasterSyncAutomatically(boolean flag) {
boolean old;
synchronized (mAuthorities) {
@@ -817,7 +909,25 @@
return id;
}
- public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+ public static boolean equals(Bundle b1, Bundle b2) {
+ if (b1.size() != b2.size()) {
+ return false;
+ }
+ if (b1.isEmpty()) {
+ return true;
+ }
+ for (String key : b1.keySet()) {
+ if (!b2.containsKey(key)) {
+ return false;
+ }
+ if (!b1.get(key).equals(b2.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void stopSyncEvent(long historyId, Bundle extras, long elapsedTime, String resultMessage,
long downstreamActivity, long upstreamActivity) {
synchronized (mAuthorities) {
if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
@@ -860,6 +970,17 @@
case SOURCE_SERVER:
status.numSourceServer++;
break;
+ case SOURCE_PERIODIC:
+ status.numSourcePeriodic++;
+ AuthorityInfo authority = mAuthorities.get(item.authorityId);
+ for (int periodicSyncIndex = 0;
+ periodicSyncIndex < authority.periodicSyncs.size();
+ periodicSyncIndex++) {
+ if (equals(extras, authority.periodicSyncs.get(periodicSyncIndex).first)) {
+ status.setPeriodicSyncTime(periodicSyncIndex, item.eventTime);
+ }
+ }
+ break;
}
boolean writeStatisticsNow = false;
@@ -948,11 +1069,27 @@
}
/**
+ * Return an array of the current authorities. Note
+ * that the objects inside the array are the real, live objects,
+ * so be careful what you do with them.
+ */
+ public ArrayList<AuthorityInfo> getAuthorities() {
+ synchronized (mAuthorities) {
+ final int N = mAuthorities.size();
+ ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
+ for (int i=0; i<N; i++) {
+ infos.add(mAuthorities.valueAt(i));
+ }
+ return infos;
+ }
+ }
+
+ /**
* Returns the status that matches the authority and account.
*
* @param account the account we want to check
* @param authority the authority whose row should be selected
- * @return the SyncStatusInfo for the authority, or null if none exists
+ * @return the SyncStatusInfo for the authority
*/
public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) {
if (account == null || authority == null) {
@@ -1130,6 +1267,12 @@
return authority;
}
+ public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
+ synchronized (mAuthorities) {
+ return getOrCreateSyncStatusLocked(authority.ident);
+ }
+ }
+
private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
SyncStatusInfo status = mSyncStatus.get(authorityId);
if (status == null) {
@@ -1155,6 +1298,25 @@
}
/**
+ * public for testing
+ */
+ public void clearAndReadState() {
+ synchronized (mAuthorities) {
+ mAuthorities.clear();
+ mAccounts.clear();
+ mPendingOperations.clear();
+ mSyncStatus.clear();
+ mSyncHistory.clear();
+
+ readAccountInfoLocked();
+ readStatusLocked();
+ readPendingOperationsLocked();
+ readStatisticsLocked();
+ readLegacyAccountInfoLocked();
+ }
+ }
+
+ /**
* Read all account information back in to the initial engine state.
*/
private void readAccountInfoLocked() {
@@ -1175,59 +1337,23 @@
mMasterSyncAutomatically = listen == null
|| Boolean.parseBoolean(listen);
eventType = parser.next();
+ AuthorityInfo authority = null;
+ Pair<Bundle, Long> periodicSync = null;
do {
- if (eventType == XmlPullParser.START_TAG
- && parser.getDepth() == 2) {
+ if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
- if ("authority".equals(tagName)) {
- int id = -1;
- try {
- id = Integer.parseInt(parser.getAttributeValue(
- null, "id"));
- } catch (NumberFormatException e) {
- } catch (NullPointerException e) {
+ if (parser.getDepth() == 2) {
+ if ("authority".equals(tagName)) {
+ authority = parseAuthority(parser);
+ periodicSync = null;
}
- if (id >= 0) {
- String accountName = parser.getAttributeValue(
- null, "account");
- String accountType = parser.getAttributeValue(
- null, "type");
- if (accountType == null) {
- accountType = "com.google";
- }
- String authorityName = parser.getAttributeValue(
- null, "authority");
- String enabled = parser.getAttributeValue(
- null, "enabled");
- String syncable = parser.getAttributeValue(null, "syncable");
- AuthorityInfo authority = mAuthorities.get(id);
- if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
- if (authority == null) {
- if (DEBUG_FILE) Log.v(TAG, "Creating entry");
- authority = getOrCreateAuthorityLocked(
- new Account(accountName, accountType),
- authorityName, id, false);
- }
- if (authority != null) {
- authority.enabled = enabled == null
- || Boolean.parseBoolean(enabled);
- if ("unknown".equals(syncable)) {
- authority.syncable = -1;
- } else {
- authority.syncable =
- (syncable == null || Boolean.parseBoolean(enabled))
- ? 1
- : 0;
- }
- } else {
- Log.w(TAG, "Failure adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
- }
+ } else if (parser.getDepth() == 3) {
+ if ("periodicSync".equals(tagName) && authority != null) {
+ periodicSync = parsePeriodicSync(parser, authority);
+ }
+ } else if (parser.getDepth() == 4 && periodicSync != null) {
+ if ("extra".equals(tagName)) {
+ parseExtra(parser, periodicSync);
}
}
}
@@ -1249,6 +1375,105 @@
}
}
+ private AuthorityInfo parseAuthority(XmlPullParser parser) {
+ AuthorityInfo authority = null;
+ int id = -1;
+ try {
+ id = Integer.parseInt(parser.getAttributeValue(
+ null, "id"));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the id of the authority", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the id of the authority is null", e);
+ }
+ if (id >= 0) {
+ String accountName = parser.getAttributeValue(null, "account");
+ String accountType = parser.getAttributeValue(null, "type");
+ if (accountType == null) {
+ accountType = "com.google";
+ }
+ String authorityName = parser.getAttributeValue(null, "authority");
+ String enabled = parser.getAttributeValue(null, "enabled");
+ String syncable = parser.getAttributeValue(null, "syncable");
+ authority = mAuthorities.get(id);
+ if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ if (authority == null) {
+ if (DEBUG_FILE) Log.v(TAG, "Creating entry");
+ authority = getOrCreateAuthorityLocked(
+ new Account(accountName, accountType), authorityName, id, false);
+ // clear this since we will read these later on
+ authority.periodicSyncs.clear();
+ }
+ if (authority != null) {
+ authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
+ if ("unknown".equals(syncable)) {
+ authority.syncable = -1;
+ } else {
+ authority.syncable =
+ (syncable == null || Boolean.parseBoolean(enabled)) ? 1 : 0;
+ }
+ } else {
+ Log.w(TAG, "Failure adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ }
+ }
+
+ return authority;
+ }
+
+ private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+ Bundle extras = new Bundle();
+ String periodValue = parser.getAttributeValue(null, "period");
+ final long period;
+ try {
+ period = Long.parseLong(periodValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the period of a periodic sync", e);
+ return null;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the period of a periodic sync is null", e);
+ return null;
+ }
+ final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
+ authority.periodicSyncs.add(periodicSync);
+ return periodicSync;
+ }
+
+ private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
+ final Bundle extras = periodicSync.first;
+ String name = parser.getAttributeValue(null, "name");
+ String type = parser.getAttributeValue(null, "type");
+ String value1 = parser.getAttributeValue(null, "value1");
+ String value2 = parser.getAttributeValue(null, "value2");
+
+ try {
+ if ("long".equals(type)) {
+ extras.putLong(name, Long.parseLong(value1));
+ } else if ("integer".equals(type)) {
+ extras.putInt(name, Integer.parseInt(value1));
+ } else if ("double".equals(type)) {
+ extras.putDouble(name, Double.parseDouble(value1));
+ } else if ("float".equals(type)) {
+ extras.putFloat(name, Float.parseFloat(value1));
+ } else if ("boolean".equals(type)) {
+ extras.putBoolean(name, Boolean.parseBoolean(value1));
+ } else if ("string".equals(type)) {
+ extras.putString(name, value1);
+ } else if ("account".equals(type)) {
+ extras.putParcelable(name, new Account(value1, value2));
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ }
+ }
+
/**
* Write all account information to the account file.
*/
@@ -1284,6 +1509,41 @@
} else if (authority.syncable == 0) {
out.attribute(null, "syncable", "false");
}
+ for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
+ out.startTag(null, "periodicSync");
+ out.attribute(null, "period", Long.toString(periodicSync.second));
+ final Bundle extras = periodicSync.first;
+ for (String key : extras.keySet()) {
+ out.startTag(null, "extra");
+ out.attribute(null, "name", key);
+ final Object value = extras.get(key);
+ if (value instanceof Long) {
+ out.attribute(null, "type", "long");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Integer) {
+ out.attribute(null, "type", "integer");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Boolean) {
+ out.attribute(null, "type", "boolean");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Float) {
+ out.attribute(null, "type", "float");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Double) {
+ out.attribute(null, "type", "double");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof String) {
+ out.attribute(null, "type", "string");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Account) {
+ out.attribute(null, "type", "account");
+ out.attribute(null, "value1", ((Account)value).name);
+ out.attribute(null, "value2", ((Account)value).type);
+ }
+ out.endTag(null, "extra");
+ }
+ out.endTag(null, "periodicSync");
+ }
out.endTag(null, "authority");
}
@@ -1389,6 +1649,7 @@
st.numSourcePoll = getIntColumn(c, "numSourcePoll");
st.numSourceServer = getIntColumn(c, "numSourceServer");
st.numSourceUser = getIntColumn(c, "numSourceUser");
+ st.numSourcePeriodic = 0;
st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
st.lastFailureSource = getIntColumn(c, "lastFailureSource");
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 808c839b..6591313 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -270,6 +270,8 @@
* Full paths to the locations of extra resource packages this application
* uses. This field is only used if there are extra resource packages,
* otherwise it is null.
+ *
+ * {@hide}
*/
public String[] resourceDirs;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 745628a..e3b1694 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -437,6 +437,15 @@
public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
/**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package couldn't be installed in the specified install
+ * location.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
+
+ /**
* Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
* if the parser was given a path that is not a file, or does not end with the expected
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index bbde0a6..bac55cc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -953,6 +953,39 @@
return null;
}
+ } else if (tagName.equals("original-package")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage);
+
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage_name);
+
+ sa.recycle();
+
+ if (name != null && (flags&PARSE_IS_SYSTEM) != 0) {
+ pkg.mOriginalPackage = name;
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("adopt-permissions")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage);
+
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage_name);
+
+ sa.recycle();
+
+ if (name != null && (flags&PARSE_IS_SYSTEM) != 0) {
+ if (pkg.mAdoptPermissions == null) {
+ pkg.mAdoptPermissions = new ArrayList<String>();
+ }
+ pkg.mAdoptPermissions.add(name);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
} else if (tagName.equals("eat-comment")) {
// Just skip this tag
XmlUtils.skipCurrentTag(parser);
@@ -2528,6 +2561,9 @@
public ArrayList<String> usesOptionalLibraries = null;
public String[] usesLibraryFiles = null;
+ public String mOriginalPackage = null;
+ public ArrayList<String> mAdoptPermissions = null;
+
// We store the application meta-data independently to avoid multiple unwanted references
public Bundle mAppMetaData = null;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 7362394..a885820 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -117,8 +117,8 @@
mContext.registerReceiver(receiver, intentFilter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(receiver, sdFilter);
}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index f64261c..7776520 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -29,7 +29,7 @@
synchronized(mLock) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
- "attempt to acquire a reference on a close SQLiteClosable");
+ "attempt to acquire a reference on an already-closed SQLiteClosable obj.");
}
mReferenceCount++;
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index e4b0191..540f4445 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -66,64 +66,60 @@
* Algorithms used in ON CONFLICT clause
* http://www.sqlite.org/lang_conflict.html
*/
- public static final class ConflictAlgorithm {
- /**
- * When a constraint violation occurs, an immediate ROLLBACK occurs,
- * thus ending the current transaction, and the command aborts with a
- * return code of SQLITE_CONSTRAINT. If no transaction is active
- * (other than the implied transaction that is created on every command)
- * then this algorithm works the same as ABORT.
- */
- public static final int ROLLBACK = 1;
+ /**
+ * When a constraint violation occurs, an immediate ROLLBACK occurs,
+ * thus ending the current transaction, and the command aborts with a
+ * return code of SQLITE_CONSTRAINT. If no transaction is active
+ * (other than the implied transaction that is created on every command)
+ * then this algorithm works the same as ABORT.
+ */
+ public static final int CONFLICT_ROLLBACK = 1;
- /**
- * When a constraint violation occurs,no ROLLBACK is executed
- * so changes from prior commands within the same transaction
- * are preserved. This is the default behavior.
- */
- public static final int ABORT = 2;
+ /**
+ * When a constraint violation occurs,no ROLLBACK is executed
+ * so changes from prior commands within the same transaction
+ * are preserved. This is the default behavior.
+ */
+ public static final int CONFLICT_ABORT = 2;
- /**
- * When a constraint violation occurs, the command aborts with a return
- * code SQLITE_CONSTRAINT. But any changes to the database that
- * the command made prior to encountering the constraint violation
- * are preserved and are not backed out.
- */
- public static final int FAIL = 3;
+ /**
+ * When a constraint violation occurs, the command aborts with a return
+ * code SQLITE_CONSTRAINT. But any changes to the database that
+ * the command made prior to encountering the constraint violation
+ * are preserved and are not backed out.
+ */
+ public static final int CONFLICT_FAIL = 3;
- /**
- * When a constraint violation occurs, the one row that contains
- * the constraint violation is not inserted or changed.
- * But the command continues executing normally. Other rows before and
- * after the row that contained the constraint violation continue to be
- * inserted or updated normally. No error is returned.
- */
- public static final int IGNORE = 4;
+ /**
+ * When a constraint violation occurs, the one row that contains
+ * the constraint violation is not inserted or changed.
+ * But the command continues executing normally. Other rows before and
+ * after the row that contained the constraint violation continue to be
+ * inserted or updated normally. No error is returned.
+ */
+ public static final int CONFLICT_IGNORE = 4;
- /**
- * When a UNIQUE constraint violation occurs, the pre-existing rows that
- * are causing the constraint violation are removed prior to inserting
- * or updating the current row. Thus the insert or update always occurs.
- * The command continues executing normally. No error is returned.
- * If a NOT NULL constraint violation occurs, the NULL value is replaced
- * by the default value for that column. If the column has no default
- * value, then the ABORT algorithm is used. If a CHECK constraint
- * violation occurs then the IGNORE algorithm is used. When this conflict
- * resolution strategy deletes rows in order to satisfy a constraint,
- * it does not invoke delete triggers on those rows.
- * This behavior might change in a future release.
- */
- public static final int REPLACE = 5;
+ /**
+ * When a UNIQUE constraint violation occurs, the pre-existing rows that
+ * are causing the constraint violation are removed prior to inserting
+ * or updating the current row. Thus the insert or update always occurs.
+ * The command continues executing normally. No error is returned.
+ * If a NOT NULL constraint violation occurs, the NULL value is replaced
+ * by the default value for that column. If the column has no default
+ * value, then the ABORT algorithm is used. If a CHECK constraint
+ * violation occurs then the IGNORE algorithm is used. When this conflict
+ * resolution strategy deletes rows in order to satisfy a constraint,
+ * it does not invoke delete triggers on those rows.
+ * This behavior might change in a future release.
+ */
+ public static final int CONFLICT_REPLACE = 5;
- /**
- * use the following when no conflict action is specified.
- */
- public static final int NONE = 0;
- private static final String[] VALUES = new String[]
- {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
-
- private ConflictAlgorithm() {} // disable instantiation of this class
- }
+ /**
+ * use the following when no conflict action is specified.
+ */
+ public static final int CONFLICT_NONE = 0;
+ private static final String[] CONFLICT_VALUES = new String[]
+ {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
/**
* Maximum Length Of A LIKE Or GLOB Pattern
@@ -290,10 +286,6 @@
@Override
protected void onAllReferencesReleased() {
if (isOpen()) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.d(TAG, "captured_sql|" + mPath + "|DETACH DATABASE " +
- getDatabaseName(mPath) + ";");
- }
if (SQLiteDebug.DEBUG_SQL_CACHE) {
mTimeClosed = getTime();
}
@@ -782,7 +774,14 @@
SQLiteDatabase db = null;
try {
// Open the database.
- return new SQLiteDatabase(path, factory, flags);
+ SQLiteDatabase sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ sqliteDatabase.enableSqlTracing(path);
+ }
+ if (SQLiteDebug.DEBUG_SQL_TIME) {
+ sqliteDatabase.enableSqlProfiling(path);
+ }
+ return sqliteDatabase;
} catch (SQLiteDatabaseCorruptException e) {
// Try to recover from this, if we can.
// TODO: should we do this for other open failures?
@@ -1338,7 +1337,7 @@
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
- return insertWithOnConflict(table, nullColumnHack, values, ConflictAlgorithm.NONE);
+ return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
@@ -1360,7 +1359,7 @@
*/
public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
throws SQLException {
- return insertWithOnConflict(table, nullColumnHack, values, ConflictAlgorithm.NONE);
+ return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
}
/**
@@ -1377,7 +1376,7 @@
public long replace(String table, String nullColumnHack, ContentValues initialValues) {
try {
return insertWithOnConflict(table, nullColumnHack, initialValues,
- ConflictAlgorithm.REPLACE);
+ CONFLICT_REPLACE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + initialValues, e);
return -1;
@@ -1399,7 +1398,7 @@
public long replaceOrThrow(String table, String nullColumnHack,
ContentValues initialValues) throws SQLException {
return insertWithOnConflict(table, nullColumnHack, initialValues,
- ConflictAlgorithm.REPLACE);
+ CONFLICT_REPLACE);
}
/**
@@ -1412,10 +1411,10 @@
* @param initialValues this map contains the initial column values for the
* row. The keys should be the column names and the values the
* column values
- * @param conflictAlgorithm {@link ConflictAlgorithm} for insert conflict resolver
+ * @param conflictAlgorithm for insert conflict resolver
* @return the row ID of the newly inserted row
* OR the primary key of the existing row if the input param 'conflictAlgorithm' =
- * {@link ConflictAlgorithm#IGNORE}
+ * {@link #CONFLICT_IGNORE}
* OR -1 if any error
*/
public long insertWithOnConflict(String table, String nullColumnHack,
@@ -1427,7 +1426,7 @@
// Measurements show most sql lengths <= 152
StringBuilder sql = new StringBuilder(152);
sql.append("INSERT");
- sql.append(ConflictAlgorithm.VALUES[conflictAlgorithm]);
+ sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
// Measurements show most values lengths < 40
@@ -1551,7 +1550,7 @@
* @return the number of rows affected
*/
public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
- return updateWithOnConflict(table, values, whereClause, whereArgs, ConflictAlgorithm.NONE);
+ return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
/**
@@ -1562,7 +1561,7 @@
* valid value that will be translated to NULL.
* @param whereClause the optional WHERE clause to apply when updating.
* Passing null will update all rows.
- * @param conflictAlgorithm {@link ConflictAlgorithm} for update conflict resolver
+ * @param conflictAlgorithm for update conflict resolver
* @return the number of rows affected
*/
public int updateWithOnConflict(String table, ContentValues values,
@@ -1577,7 +1576,7 @@
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
- sql.append(ConflictAlgorithm.VALUES[conflictAlgorithm]);
+ sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(table);
sql.append(" SET ");
@@ -1652,9 +1651,6 @@
*/
public void execSQL(String sql) throws SQLException {
long timeStart = Debug.threadCpuTimeNanos();
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(this.getPath(), sql, null));
- }
lock();
try {
native_execSQL(sql);
@@ -1680,9 +1676,6 @@
if (bindArgs == null) {
throw new IllegalArgumentException("Empty bindArgs");
}
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(this.getPath(), sql, bindArgs));
- }
long timeStart = Debug.threadCpuTimeNanos();
lock();
SQLiteStatement statement = null;
@@ -1741,10 +1734,6 @@
mLeakedException = new IllegalStateException(path +
" SQLiteDatabase created and never closed");
mFactory = factory;
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.d(TAG, "captured_sql|" + mPath + "|ATTACH DATABASE '" + mPath +
- "' as " + getDatabaseName(mPath) + ";");
- }
dbopen(mPath, mFlags);
if (SQLiteDebug.DEBUG_SQL_CACHE) {
mTimeOpened = getTime();
@@ -1754,10 +1743,6 @@
setLocale(Locale.getDefault());
} catch (RuntimeException e) {
Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.d(TAG, "captured_sql|" + mPath + "|DETACH DATABASE " +
- getDatabaseName(mPath) + ";");
- }
dbclose();
if (SQLiteDebug.DEBUG_SQL_CACHE) {
mTimeClosed = getTime();
@@ -1770,20 +1755,6 @@
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
}
- private String getDatabaseName(String path) {
- if (path == null || path.trim().length() == 0) {
- return "db not specified?";
- }
-
- if (path.equalsIgnoreCase(":memory:")) {
- return "memorydb";
- }
- String[] tokens = path.split("/");
- String[] lastNodeTokens = tokens[tokens.length - 1].split("\\.", 2);
- return (lastNodeTokens.length == 1) ? lastNodeTokens[0]
- : lastNodeTokens[0] + lastNodeTokens[1];
- }
-
/**
* return whether the DB is opened as read only.
* @return true if DB is opened as read only
@@ -1922,8 +1893,9 @@
mCacheFullWarnings = 0;
// clear the cache
mCompiledQueries.clear();
- Log.w(TAG, "compiled-sql statement cache cleared for the database " +
- getPath());
+ Log.w(TAG, "Compiled-sql statement cache for database: " +
+ getPath() + " hit MAX size-limit too many times. " +
+ "Removing all compiled-sql statements from the cache.");
} else {
// clear just a single entry from cache
Set<String> keySet = mCompiledQueries.keySet();
@@ -2061,6 +2033,23 @@
private native void dbopen(String path, int flags);
/**
+ * Native call to setup tracing of all sql statements
+ *
+ * @param path the full path to the database
+ */
+ private native void enableSqlTracing(String path);
+
+ /**
+ * Native call to setup profiling of all sql statements.
+ * currently, sqlite's profiling = printing of execution-time
+ * (wall-clock time) of each of the sql statements, as they
+ * are executed.
+ *
+ * @param path the full path to the database
+ */
+ private native void enableSqlProfiling(String path);
+
+ /**
* Native call to execute a raw SQL statement. {@link #lock} must be held
* when calling this method.
*
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index b12034a..4ea680e 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -31,19 +31,19 @@
Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
+ * Controls the printing of wall-clock time taken to execute SQL statements
+ * as they are executed.
+ */
+ public static final boolean DEBUG_SQL_TIME =
+ Log.isLoggable("SQLiteTime", Log.VERBOSE);
+
+ /**
* Controls the printing of compiled-sql-statement cache stats.
*/
public static final boolean DEBUG_SQL_CACHE =
Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
/**
- * Controls the capturing and printing of complete sql statement including the bind args and
- * the database name.
- */
- public static final boolean DEBUG_CAPTURE_SQL =
- Log.isLoggable("SQLiteCaptureSql", Log.VERBOSE);
-
- /**
* Controls the stack trace reporting of active cursors being
* finalized.
*/
@@ -121,62 +121,4 @@
static synchronized void notifyActiveCursorFinalized() {
sNumActiveCursorsFinalized++;
}
-
- /**
- * returns a message containing the given database name (path) and the string built by
- * replacing "?" characters in the given sql string with the corresponding
- * positional values from the given param bindArgs.
- *
- * @param path the database name
- * @param sql sql string with possibly "?" for bindargs
- * @param bindArgs args for "?"s in the above string
- * @return the String to be logged
- */
- /* package */ static String captureSql(String path, String sql, Object[] bindArgs) {
- // how many bindargs in sql
- sql = sql.trim();
- String args[] = sql.split("\\?");
- // how many "?"s in the given sql string?
- int varArgsInSql = (sql.endsWith("?")) ? args.length : args.length - 1;
-
- // how many bind args do we have in the given input param bindArgs
- int bindArgsLen = (bindArgs == null) ? 0 : bindArgs.length;
- if (varArgsInSql < bindArgsLen) {
- return "too many bindArgs provided. " +
- "# of bindArgs = " + bindArgsLen + ", # of varargs = " + varArgsInSql +
- "; sql = " + sql;
- }
-
- // if there are no bindArgs, we are done. log the sql as is.
- if (bindArgsLen == 0 && varArgsInSql == 0) {
- return logSql(path, sql);
- }
-
- StringBuilder buf = new StringBuilder();
-
- // take the supplied bindArgs and plug them into sql
- for (int i = 0; i < bindArgsLen; i++) {
- buf.append(args[i]);
- buf.append(bindArgs[i]);
- }
-
- // does given sql have more varArgs than the supplied bindArgs
- // if so, assign nulls to the extra varArgs in sql
- for (int i = bindArgsLen; i < varArgsInSql; i ++) {
- buf.append(args[i]);
- buf.append("null");
- }
-
- // if there are any characters left in the given sql string AFTER the last "?"
- // log them also. for example, if the given sql = "select * from test where a=? and b=1
- // then the following code appends " and b=1" string to buf.
- if (varArgsInSql < args.length) {
- buf.append(args[varArgsInSql]);
- }
- return logSql(path, buf.toString());
- }
-
- private static String logSql(String path, String sql) {
- return "captured_sql|" + path + "|" + sql + ";";
- }
}
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 1159c1d..2d0aa39 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -16,19 +16,10 @@
package android.database.sqlite;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import android.util.Log;
-
/**
* A base class for compiled SQLite programs.
*/
public abstract class SQLiteProgram extends SQLiteClosable {
- private static final String TAG = "SQLiteProgram";
/** The database this program is compiled against. */
protected SQLiteDatabase mDatabase;
@@ -53,16 +44,7 @@
*/
protected int nStatement = 0;
- /**
- * stores all bindargs for debugging purposes
- */
- private Map<Integer, String> mBindArgs = null;
-
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.d(TAG, "processing sql: " + sql);
- }
-
mDatabase = db;
mSql = sql;
db.acquireReference();
@@ -120,7 +102,7 @@
}
/**
- * @deprecated use this.compiledStatement.compile instead
+ * @deprecated This method is deprecated and must not be used.
*
* @param sql the SQL string to compile
* @param forceCompilation forces the SQL to be recompiled in the event that there is an
@@ -138,9 +120,6 @@
* @param index The 1-based index to the parameter to bind null to
*/
public void bindNull(int index) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, "null");
- }
acquireReference();
try {
native_bind_null(index);
@@ -157,9 +136,6 @@
* @param value The value to bind
*/
public void bindLong(int index, long value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, value + "");
- }
acquireReference();
try {
native_bind_long(index, value);
@@ -176,9 +152,6 @@
* @param value The value to bind
*/
public void bindDouble(int index, double value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, value + "");
- }
acquireReference();
try {
native_bind_double(index, value);
@@ -195,9 +168,6 @@
* @param value The value to bind
*/
public void bindString(int index, String value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, "'" + value + "'");
- }
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
@@ -217,9 +187,6 @@
* @param value The value to bind
*/
public void bindBlob(int index, byte[] value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, "blob");
- }
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
@@ -235,9 +202,6 @@
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
public void clearBindings() {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- mBindArgs = null;
- }
acquireReference();
try {
native_clear_bindings();
@@ -259,39 +223,7 @@
}
/**
- * this method is called under the debug flag {@link SQLiteDebug.DEBUG_CAPTURE_SQL} only.
- * it collects the bindargs as they are called by the callers the bind... methods in this
- * class.
- */
- private void addToBindArgs(int index, String obj) {
- if (mBindArgs == null) {
- mBindArgs = new HashMap<Integer, String>();
- }
- mBindArgs.put(index, obj);
- }
-
- /**
- * constructs all the bindargs in sequence and returns a String Array of the values.
- * it uses the HashMap built up by the above method.
- *
- * @return the string array of bindArgs with the args arranged in sequence
- */
- /* package */ String[] getBindArgs() {
- if (mBindArgs == null) {
- return null;
- }
- Set<Integer> indexSet = mBindArgs.keySet();
- ArrayList<Integer> indexList = new ArrayList<Integer>(indexSet);
- Collections.sort(indexList);
- int len = indexList.size();
- String[] bindObjs = new String[len];
- for (int i = 0; i < len; i++) {
- bindObjs[i] = mBindArgs.get(indexList.get(i));
- }
- return bindObjs;
- }
-
- /**
+ * @deprecated This method is deprecated and must not be used.
* Compiles SQL into a SQLite program.
*
* <P>The database lock must be held when calling this method.
@@ -299,6 +231,10 @@
*/
@Deprecated
protected final native void native_compile(String sql);
+
+ /**
+ * @deprecated This method is deprecated and must not be used.
+ */
@Deprecated
protected final native void native_finalize();
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index c34661d..5bcad4b 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -18,7 +18,6 @@
import android.database.CursorWindow;
import android.os.Debug;
-import android.os.SystemClock;
import android.util.Log;
/**
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 0cee3c5..f1f5a2a 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -17,7 +17,6 @@
package android.database.sqlite;
import android.os.Debug;
-import android.util.Log;
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
@@ -27,8 +26,6 @@
*/
public class SQLiteStatement extends SQLiteProgram
{
- private static final String TAG = "SQLiteStatement";
-
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
@@ -52,12 +49,6 @@
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "execute() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
} finally {
@@ -82,12 +73,6 @@
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "executeInsert() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
return mDatabase.lastInsertRow();
@@ -111,12 +96,6 @@
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
long retValue = native_1x1_long();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
@@ -140,12 +119,6 @@
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "simpleQueryForString() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
String retValue = native_1x1_string();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java
index d71344c..300cd28 100755
--- a/core/java/android/gesture/Gesture.java
+++ b/core/java/android/gesture/Gesture.java
@@ -293,7 +293,7 @@
} catch (IOException e) {
Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e);
} finally {
- GestureUtilities.closeStream(inStream);
+ GestureUtils.closeStream(inStream);
}
if (gesture != null) {
@@ -322,8 +322,8 @@
} catch (IOException e) {
Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e);
} finally {
- GestureUtilities.closeStream(outStream);
- GestureUtilities.closeStream(byteStream);
+ GestureUtils.closeStream(outStream);
+ GestureUtils.closeStream(byteStream);
}
if (result) {
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
index 30ecf5a..b6c260f 100755
--- a/core/java/android/gesture/GestureOverlayView.java
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -638,7 +638,7 @@
if (mTotalLength > mGestureStrokeLengthThreshold) {
final OrientedBoundingBox box =
- GestureUtilities.computeOrientedBoundingBox(mStrokeBuffer);
+ GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
float angle = Math.abs(box.orientation);
if (angle > 90) {
diff --git a/core/java/android/gesture/GestureStore.java b/core/java/android/gesture/GestureStore.java
index 11a94d1..11b5044 100644
--- a/core/java/android/gesture/GestureStore.java
+++ b/core/java/android/gesture/GestureStore.java
@@ -264,7 +264,7 @@
mChanged = false;
} finally {
- if (closeStream) GestureUtilities.closeStream(out);
+ if (closeStream) GestureUtils.closeStream(out);
}
}
@@ -299,7 +299,7 @@
Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
}
} finally {
- if (closeStream) GestureUtilities.closeStream(in);
+ if (closeStream) GestureUtils.closeStream(in);
}
}
diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java
index c3ddb28..1d0f0fe 100644
--- a/core/java/android/gesture/GestureStroke.java
+++ b/core/java/android/gesture/GestureStroke.java
@@ -159,15 +159,15 @@
* @return the path
*/
public Path toPath(float width, float height, int numSample) {
- final float[] pts = GestureUtilities.temporalSampling(this, numSample);
+ final float[] pts = GestureUtils.temporalSampling(this, numSample);
final RectF rect = boundingBox;
- GestureUtilities.translate(pts, -rect.left, -rect.top);
+ GestureUtils.translate(pts, -rect.left, -rect.top);
float sx = width / rect.width();
float sy = height / rect.height();
float scale = sx > sy ? sy : sx;
- GestureUtilities.scale(pts, scale, scale);
+ GestureUtils.scale(pts, scale, scale);
float mX = 0;
float mY = 0;
@@ -241,6 +241,6 @@
* @return OrientedBoundingBox
*/
public OrientedBoundingBox computeOrientedBoundingBox() {
- return GestureUtilities.computeOrientedBoundingBox(points);
+ return GestureUtils.computeOrientedBoundingBox(points);
}
}
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtils.java
similarity index 99%
rename from core/java/android/gesture/GestureUtilities.java
rename to core/java/android/gesture/GestureUtils.java
index 9d95ce4..dd221fc 100755
--- a/core/java/android/gesture/GestureUtilities.java
+++ b/core/java/android/gesture/GestureUtils.java
@@ -36,12 +36,12 @@
* distances between two gestures).
* </ul>
*/
-public final class GestureUtilities {
+public final class GestureUtils {
private static final float SCALING_THRESHOLD = 0.26f;
private static final float NONUNIFORM_SCALE = (float) Math.sqrt(2);
- private GestureUtilities() {
+ private GestureUtils() {
}
/**
diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java
index bb0b340..02a6519 100755
--- a/core/java/android/gesture/Instance.java
+++ b/core/java/android/gesture/Instance.java
@@ -84,13 +84,13 @@
}
private static float[] spatialSampler(Gesture gesture) {
- return GestureUtilities.spatialSampling(gesture, PATCH_SAMPLE_SIZE, false);
+ return GestureUtils.spatialSampling(gesture, PATCH_SAMPLE_SIZE, false);
}
private static float[] temporalSampler(int orientationType, Gesture gesture) {
- float[] pts = GestureUtilities.temporalSampling(gesture.getStrokes().get(0),
+ float[] pts = GestureUtils.temporalSampling(gesture.getStrokes().get(0),
SEQUENCE_SAMPLE_SIZE);
- float[] center = GestureUtilities.computeCentroid(pts);
+ float[] center = GestureUtils.computeCentroid(pts);
float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]);
float adjustment = -orientation;
@@ -104,8 +104,8 @@
}
}
- GestureUtilities.translate(pts, -center[0], -center[1]);
- GestureUtilities.rotate(pts, adjustment);
+ GestureUtils.translate(pts, -center[0], -center[1]);
+ GestureUtils.rotate(pts, adjustment);
return pts;
}
diff --git a/core/java/android/gesture/InstanceLearner.java b/core/java/android/gesture/InstanceLearner.java
index 9987e69..7224ded 100644
--- a/core/java/android/gesture/InstanceLearner.java
+++ b/core/java/android/gesture/InstanceLearner.java
@@ -53,9 +53,9 @@
}
double distance;
if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
- distance = GestureUtilities.minimumCosineDistance(sample.vector, vector, orientationType);
+ distance = GestureUtils.minimumCosineDistance(sample.vector, vector, orientationType);
} else {
- distance = GestureUtilities.squaredEuclideanDistance(sample.vector, vector);
+ distance = GestureUtils.squaredEuclideanDistance(sample.vector, vector);
}
double weight;
if (distance == 0) {
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 0c6bb1a..8eed9f7 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -287,6 +287,7 @@
static private class SensorThread {
Thread mThread;
+ boolean mSensorsReady;
SensorThread() {
// this gets to the sensor module. We can have only one per process.
@@ -299,17 +300,28 @@
}
// must be called with sListeners lock
- void startLocked(ISensorService service) {
+ boolean startLocked(ISensorService service) {
try {
if (mThread == null) {
Bundle dataChannel = service.getDataChannel();
- mThread = new Thread(new SensorThreadRunnable(dataChannel),
- SensorThread.class.getName());
- mThread.start();
+ if (dataChannel != null) {
+ mSensorsReady = false;
+ SensorThreadRunnable runnable = new SensorThreadRunnable(dataChannel);
+ Thread thread = new Thread(runnable, SensorThread.class.getName());
+ thread.start();
+ synchronized (runnable) {
+ while (mSensorsReady == false) {
+ runnable.wait();
+ }
+ }
+ mThread = thread;
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in startLocked: ", e);
+ } catch (InterruptedException e) {
}
+ return mThread == null ? false : true;
}
private class SensorThreadRunnable implements Runnable {
@@ -319,13 +331,9 @@
}
private boolean open() {
- if (mDataChannel == null) {
- Log.e(TAG, "mDataChannel == NULL, exiting");
- synchronized (sListeners) {
- mThread = null;
- }
- return false;
- }
+ // NOTE: this cannot synchronize on sListeners, since
+ // it's held in the main thread at least until we
+ // return from here.
// this thread is guaranteed to be unique
Parcelable[] pfds = mDataChannel.getParcelableArray("fds");
@@ -370,6 +378,12 @@
return;
}
+ synchronized (this) {
+ // we've open the driver, we're ready to open the sensors
+ mSensorsReady = true;
+ this.notify();
+ }
+
while (true) {
// wait for an event
final int sensor = sensors_data_poll(values, status, timestamp);
@@ -907,14 +921,18 @@
String name = sensor.getName();
int handle = sensor.getHandle();
if (l == null) {
+ result = false;
l = new ListenerDelegate(listener, sensor, handler);
- result = mSensorService.enableSensor(l, name, handle, delay);
- if (result) {
- sListeners.add(l);
- sListeners.notify();
- }
+ sListeners.add(l);
if (!sListeners.isEmpty()) {
- sSensorThread.startLocked(mSensorService);
+ result = sSensorThread.startLocked(mSensorService);
+ if (result) {
+ result = mSensorService.enableSensor(l, name, handle, delay);
+ if (!result) {
+ // there was an error, remove the listeners
+ sListeners.remove(l);
+ }
+ }
}
} else {
result = mSensorService.enableSensor(l, name, handle, delay);
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
old mode 100755
new mode 100644
index b0c3909..28a86b8
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -620,7 +620,10 @@
if (mBuffer == null || mKeyboardChanged) {
if (mBuffer == null || mKeyboardChanged &&
(mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
- mBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
+ // Make sure our bitmap is at least 1x1
+ final int width = Math.max(1, getWidth());
+ final int height = Math.max(1, getHeight());
+ mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBuffer);
}
invalidateAllKeys();
@@ -739,6 +742,10 @@
final Key key = keys[nearestKeyIndices[i]];
int dist = 0;
boolean isInside = key.isInside(x,y);
+ if (isInside) {
+ primaryIndex = nearestKeyIndices[i];
+ }
+
if (((mProximityCorrectOn
&& (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
|| isInside)
@@ -767,10 +774,6 @@
}
}
}
-
- if (isInside) {
- primaryIndex = nearestKeyIndices[i];
- }
}
if (primaryIndex == NOT_A_KEY) {
primaryIndex = closestKey;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 30799ec..d435df5 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -116,6 +116,24 @@
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
/**
+ * Broadcast Action: A tetherable connection has come or gone
+ * TODO - finish the doc
+ * @hide
+ */
+ public static final String ACTION_TETHER_STATE_CHANGED =
+ "android.net.conn.TETHER_STATE_CHANGED";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_AVAILABLE_TETHER_COUNT = "availableCount";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_ACTIVE_TETHER_COUNT = "activeCount";
+
+ /**
* The Default Mobile data connection. When active, all data traffic
* will use this connection by default. Should not coexist with other
* default connections.
@@ -338,4 +356,48 @@
}
mService = service;
}
+
+ /**
+ * {@hide}
+ */
+ public String[] getTetherableIfaces() {
+ try {
+ return mService.getTetherableIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public String[] getTetheredIfaces() {
+ try {
+ return mService.getTetheredIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public boolean tether(String iface) {
+ try {
+ return mService.tether(iface);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public boolean untether(String iface) {
+ try {
+ return mService.untether(iface);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 9f59cce..caa3f2b 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -50,4 +50,12 @@
boolean getBackgroundDataSetting();
void setBackgroundDataSetting(boolean allowBackgroundData);
+
+ boolean tether(String iface);
+
+ boolean untether(String iface);
+
+ String[] getTetherableIfaces();
+
+ String[] getTetheredIfaces();
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 62e9f1f..ad8e2bf 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -54,7 +54,7 @@
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static long getMobileTxPkts() {
+ public static long getMobileTxPackets() {
return getMobileStat(MOBILE_TX_PACKETS);
}
@@ -64,7 +64,7 @@
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static long getMobileRxPkts() {
+ public static long getMobileRxPackets() {
return getMobileStat(MOBILE_RX_PACKETS);
}
@@ -94,7 +94,7 @@
* @return the number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static long getTotalTxPkts() {
+ public static long getTotalTxPackets() {
return getTotalStat("tx_packets");
}
@@ -104,7 +104,7 @@
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static long getTotalRxPkts() {
+ public static long getTotalRxPackets() {
return getTotalStat("rx_packets");
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 9491bd4..ef1f3be 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,7 +18,7 @@
import java.io.File;
-import android.os.IMountService;
+import android.os.storage.IMountService;
/**
* Provides access to environment variables.
diff --git a/core/java/android/os/IMountServiceListener.aidl b/core/java/android/os/IMountServiceListener.aidl
deleted file mode 100644
index 3df64b2..0000000
--- a/core/java/android/os/IMountServiceListener.aidl
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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 android.os;
-
-/**
- * Callback class for receiving events from MountService.
- *
- * @hide
- */
-interface IMountServiceListener {
- /**
- * A sharing method has changed availability state.
- *
- * @param method The share method which has changed.
- * @param available The share availability state.
- */
- void onShareAvailabilityChanged(String method, boolean available);
-
- /**
- * Media has been inserted
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- */
- void onMediaInserted(String label, String path, int major, int minor);
-
- /**
- * Media has been removed
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- * @param clean Indicates if the removal was clean (unmounted first).
- */
- void onMediaRemoved(String label, String path, int major, int minor, boolean clean);
-
- /**
- * Volume state has changed.
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param oldState The old state of the volume.
- * @param newState The new state of the volume.
- *
- * Note: State is one of the values returned by Environment.getExternalStorageState()
- */
- void onVolumeStateChanged(String label, String path, String oldState, String newState);
-
-}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index e4ec098..f48f45f 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -140,7 +140,8 @@
* Attaches a PPP server daemon to the specified TTY with the specified
* local/remote addresses.
*/
- void attachPppd(String tty, String localAddr, String remoteAddr);
+ void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr,
+ String dns2Addr);
/**
* Detaches a PPP server daemon from the specified TTY.
diff --git a/core/java/android/os/MountServiceListener.java b/core/java/android/os/MountServiceListener.java
deleted file mode 100644
index a68f464..0000000
--- a/core/java/android/os/MountServiceListener.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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;
-
-/**
- * Callback class for receiving progress reports during a restore operation. These
- * methods will all be called on your application's main thread.
- * @hide
- */
-public abstract class MountServiceListener {
- /**
- * A sharing method has changed availability state.
- *
- * @param method The share method which has changed.
- * @param available The share availability state.
- */
- void shareAvailabilityChange(String method, boolean available) {
- }
-
- /**
- * Media has been inserted
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- */
- void mediaInserted(String label, String path, int major, int minor) {
- }
-
- /**
- * Media has been removed
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- * @param clean Indicates if the removal was clean (unmounted first).
- */
- void mediaRemoved(String label, String path, int major, int minor, boolean clean) {
- }
-
- /**
- * Volume state has changed.
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param oldState The old state of the volume.
- * @param newState The new state of the volume.
- *
- * Note: State is one of the values returned by Environment.getExternalStorageState()
- */
- void volumeStateChange(String label, String path, String oldState, String newState) {
- }
-}
diff --git a/core/java/android/os/MountServiceResultCode.java b/core/java/android/os/MountServiceResultCode.java
deleted file mode 100644
index e71dbf4..0000000
--- a/core/java/android/os/MountServiceResultCode.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 android.os;
-
-import java.io.IOException;
-
-/**
- * Class that provides access to constants returned from MountService APIs
- *
- * {@hide}
- */
-public class MountServiceResultCode
-{
- public static final int OperationSucceeded = 0;
- public static final int OperationFailedInternalError = -1;
- public static final int OperationFailedNoMedia = -2;
- public static final int OperationFailedMediaBlank = -3;
- public static final int OperationFailedMediaCorrupt = -4;
- public static final int OperationFailedVolumeNotMounted = -5;
-}
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
index bc76180..b3df522 100644
--- a/core/java/android/os/Power.java
+++ b/core/java/android/os/Power.java
@@ -18,7 +18,7 @@
import java.io.IOException;
import android.os.ServiceManager;
-import android.os.IMountService;
+import android.os.storage.IMountService;
/**
* Class that provides access to some of the power management functions.
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
similarity index 82%
rename from core/java/android/os/IMountService.aidl
rename to core/java/android/os/storage/IMountService.aidl
index a5828f6..84e3f58 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/storage/IMountService.aidl
@@ -15,14 +15,15 @@
** limitations under the License.
*/
-package android.os;
+package android.os.storage;
-import android.os.IMountServiceListener;
+import android.os.storage.IMountServiceListener;
/** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
* In particular, the ordering of the methods below must match the
* _TRANSACTION enum in IMountService.cpp
- * @hide
+ * @hide - Applications should use android.os.storage.StorageManager to access
+ * storage functions.
*/
interface IMountService
{
@@ -38,31 +39,19 @@
void unregisterListener(IMountServiceListener listener);
/**
- * Gets an Array of supported share methods
+ * Returns true if a USB mass storage host is connected
*/
- String[] getShareMethodList();
+ boolean isUsbMassStorageConnected();
/**
- * Returns true if the share method is available
+ * Enables / disables USB mass storage.
*/
- boolean getShareMethodAvailable(String method);
+ int setUsbMassStorageEnabled(boolean enable);
/**
- * Shares a volume via the specified method
- * Returns an int consistent with MountServiceResultCode
+ * Returns true if a USB mass storage host is enabled (media is shared)
*/
- int shareVolume(String path, String method);
-
- /**
- * Unshares a volume via the specified method
- * Returns an int consistent with MountServiceResultCode
- */
- int unshareVolume(String path, String method);
-
- /**
- * Returns true if the volume is shared via the specified method.
- */
- boolean getVolumeShared(String path, String method);
+ boolean isUsbMassStorageEnabled();
/**
* Mount external storage at given mount point.
diff --git a/core/java/android/os/storage/IMountServiceListener.aidl b/core/java/android/os/storage/IMountServiceListener.aidl
new file mode 100644
index 0000000..883413a
--- /dev/null
+++ b/core/java/android/os/storage/IMountServiceListener.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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 android.os.storage;
+
+/**
+ * Callback class for receiving events from MountService.
+ *
+ * @hide - Applications should use android.os.storage.IStorageEventListener
+ * for storage event callbacks.
+ */
+interface IMountServiceListener {
+ /**
+ * Detection state of USB Mass Storage has changed
+ *
+ * @param available true if a UMS host is connected.
+ */
+ void onUsbMassStorageConnectionChanged(boolean connected);
+
+ /**
+ * Storage state has changed.
+ *
+ * @param path The volume mount path.
+ * @param oldState The old state of the volume.
+ * @param newState The new state of the volume.
+ *
+ * Note: State is one of the values returned by Environment.getExternalStorageState()
+ */
+ void onStorageStateChanged(String path, String oldState, String newState);
+}
diff --git a/core/java/android/os/storage/MountServiceListener.java b/core/java/android/os/storage/MountServiceListener.java
new file mode 100644
index 0000000..bebb3f6
--- /dev/null
+++ b/core/java/android/os/storage/MountServiceListener.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+/**
+ * Callback class for receiving progress reports during a restore operation. These
+ * methods will all be called on your application's main thread.
+ * @hide
+ */
+public abstract class MountServiceListener {
+ /**
+ * USB Mass storage connection state has changed.
+ *
+ * @param connected True if UMS is connected.
+ */
+ void onUsbMassStorageConnectionChanged(boolean connected) {
+ }
+
+ /**
+ * Storage state has changed.
+ *
+ * @param path The volume mount path.
+ * @param oldState The old state of the volume.
+ * @param newState The new state of the volume.
+ *
+ * @Note: State is one of the values returned by Environment.getExternalStorageState()
+ */
+ void onStorageStateChange(String path, String oldState, String newState) {
+ }
+}
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
new file mode 100644
index 0000000..d3d39d6d
--- /dev/null
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+package android.os.storage;
+
+/**
+ * Used for receiving notifications from the StorageManager
+ */
+public abstract class StorageEventListener {
+ /**
+ * Called when the detection state of a USB Mass Storage host has changed.
+ * @param connected true if the USB mass storage is connected.
+ */
+ public void onUsbMassStorageConnectionChanged(boolean connected) {
+ }
+
+ /**
+ * Called when storage has changed state
+ * @param path the filesystem path for the storage
+ * @param oldState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
+ * @param newState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
+ */
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ }
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
new file mode 100644
index 0000000..e421ea5
--- /dev/null
+++ b/core/java/android/os/storage/StorageManager.java
@@ -0,0 +1,297 @@
+/*
+ * 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.
+ */
+
+package android.os.storage;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.storage.IMountService;
+import android.os.storage.IMountServiceListener;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * StorageManager is the interface to the systems storage service.
+ * 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}.
+ *
+ */
+
+public class StorageManager
+{
+ private static final String TAG = "StorageManager";
+
+ /*
+ * Our internal MountService binder reference
+ */
+ private IMountService mMountService;
+
+ /*
+ * The looper target for callbacks
+ */
+ Looper mTgtLooper;
+
+ /*
+ * Target listener for binder callbacks
+ */
+ private MountServiceBinderListener mBinderListener;
+
+ /*
+ * List of our listeners
+ */
+ private ArrayList<ListenerDelegate> mListeners = new ArrayList<ListenerDelegate>();
+
+ private class MountServiceBinderListener extends IMountServiceListener.Stub {
+ public void onUsbMassStorageConnectionChanged(boolean available) {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; i++) {
+ mListeners.get(i).sendShareAvailabilityChanged(available);
+ }
+ }
+
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; i++) {
+ mListeners.get(i).sendStorageStateChanged(path, oldState, newState);
+ }
+ }
+ }
+
+ /**
+ * Private base class for messages sent between the callback thread
+ * 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;
+
+ private Message mMessage;
+
+ public StorageEvent(int what) {
+ mMessage = Message.obtain();
+ mMessage.what = what;
+ mMessage.obj = this;
+ }
+
+ public Message getMessage() {
+ return mMessage;
+ }
+ }
+
+ /**
+ * Message sent on a USB mass storage connection change.
+ */
+ private class UmsConnectionChangedStorageEvent extends StorageEvent {
+ public boolean available;
+
+ public UmsConnectionChangedStorageEvent(boolean a) {
+ super(EVENT_UMS_CONNECTION_CHANGED);
+ available = a;
+ }
+ }
+
+ /**
+ * Message sent on volume state change.
+ */
+ private class StorageStateChangedStorageEvent extends StorageEvent {
+ public String path;
+ public String oldState;
+ public String newState;
+
+ public StorageStateChangedStorageEvent(String p, String oldS, String newS) {
+ super(EVENT_STORAGE_STATE_CHANGED);
+ path = p;
+ oldState = oldS;
+ newState = newS;
+ }
+ }
+
+ /**
+ * Private class containing sender and receiver code for StorageEvents.
+ */
+ private class ListenerDelegate {
+ final StorageEventListener mStorageEventListener;
+ private final Handler mHandler;
+
+ ListenerDelegate(StorageEventListener listener) {
+ mStorageEventListener = listener;
+ mHandler = new Handler(mTgtLooper) {
+ @Override
+ public void handleMessage(Message msg) {
+ StorageEvent e = (StorageEvent) msg.obj;
+
+ if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) {
+ UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e;
+ mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available);
+ } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) {
+ StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
+ mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);
+ } else {
+ Log.e(TAG, "Unsupported event " + msg.what);
+ }
+ }
+ };
+ }
+
+ StorageEventListener getListener() {
+ return mStorageEventListener;
+ }
+
+ void sendShareAvailabilityChanged(boolean available) {
+ UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available);
+ mHandler.sendMessage(e.getMessage());
+ }
+
+ void sendStorageStateChanged(String path, String oldState, String newState) {
+ StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
+ mHandler.sendMessage(e.getMessage());
+ }
+ }
+
+ /**
+ * Constructs a StorageManager object through which an application can
+ * can communicate with the systems mount service.
+ *
+ * @param tgtLooper The {@android.os.Looper} which events will be received on.
+ *
+ * <p>Applications can get instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
+ * of {@link android.content.Context#STORAGE_SERVICE}.
+ *
+ * @hide
+ */
+ public StorageManager(Looper tgtLooper) throws RemoteException {
+ mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
+ if (mMountService == null) {
+ Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
+ return;
+ }
+ mTgtLooper = tgtLooper;
+ mBinderListener = new MountServiceBinderListener();
+ mMountService.registerListener(mBinderListener);
+ }
+
+
+ /**
+ * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
+ *
+ * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
+ *
+ */
+ public void registerListener(StorageEventListener listener) {
+ if (listener == null) {
+ return;
+ }
+
+ synchronized (mListeners) {
+ mListeners.add(new ListenerDelegate(listener));
+ }
+ }
+
+ /**
+ * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
+ *
+ * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
+ *
+ */
+ public void unregisterListener(StorageEventListener listener) {
+ if (listener == null) {
+ return;
+ }
+
+ synchronized (mListeners) {
+ final int size = mListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate l = mListeners.get(i);
+ if (l.getListener() == listener) {
+ mListeners.remove(i);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Enables USB Mass Storage (UMS) on the device.
+ * @return an integer value representing the outcome of the operation.
+ * @see android.os.storage.StorageResultCode
+ */
+ public int enableUsbMassStorage() {
+ try {
+ return mMountService.setUsbMassStorageEnabled(true);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to enable UMS", ex);
+ }
+ return StorageResultCode.OperationFailedInternalError;
+ }
+
+ /**
+ * Disables USB Mass Storage (UMS) on the device.
+ * @return an integer value representing the outcome of the operation.
+ * @see android.os.storage.StorageResultCode
+ */
+ public int disableUsbMassStorage() {
+ try {
+ return mMountService.setUsbMassStorageEnabled(false);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to disable UMS", ex);
+ }
+ return StorageResultCode.OperationFailedInternalError;
+ }
+
+ /**
+ * Query if a USB Mass Storage (UMS) host is connected.
+ * @return true if UMS host is connected.
+ */
+ public boolean isUsbMassStorageConnected() {
+ try {
+ return mMountService.isUsbMassStorageConnected();
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to get UMS connection state", ex);
+ }
+ return false;
+ }
+
+ /**
+ * Query if a USB Mass Storage (UMS) is enabled on the device.
+ * @return true if UMS host is enabled.
+ */
+ public boolean isUsbMassStorageEnabled() {
+ try {
+ return mMountService.isUsbMassStorageEnabled();
+ } catch (RemoteException rex) {
+ Log.e(TAG, "Failed to get UMS enable state", rex);
+ }
+ return false;
+ }
+}
diff --git a/core/java/android/os/storage/StorageResultCode.java b/core/java/android/os/storage/StorageResultCode.java
new file mode 100644
index 0000000..584f160
--- /dev/null
+++ b/core/java/android/os/storage/StorageResultCode.java
@@ -0,0 +1,60 @@
+/*
+ * 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 android.os.storage;
+
+/**
+ * Class that provides access to constants returned from StorageManager
+ * and lower level MountService APIs.
+ */
+public class StorageResultCode
+{
+ /**
+ * Operation succeeded.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationSucceeded = 0;
+
+ /**
+ * Operation failed: Internal error.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedInternalError = -1;
+
+ /**
+ * Operation failed: Missing media.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedNoMedia = -2;
+
+ /**
+ * Operation failed: Media is blank.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedMediaBlank = -3;
+
+ /**
+ * Operation failed: Media is corrupt.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedMediaCorrupt = -4;
+
+ /**
+ * Operation failed: Media not mounted.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedVolumeNotMounted = -5;
+}
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index 09ac1fd..0a6415d 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -689,7 +689,9 @@
final int length = phoneNumber.length();
for (int i = 0; i < length; i++) {
final char ch = phoneNumber.charAt(i);
- if (Character.isDigit(ch)) {
+ // TODO: add a test case for string with '+', and care the other possible issues
+ // which may happen by ignoring non-digits other than '+'.
+ if (Character.isDigit(ch) || ch == '+') {
builder.append(ch);
} else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
phoneList.add(builder.toString());
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f89ba91..cb42d73 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -184,6 +184,20 @@
* <P>Type: INTEGER (long)</P>
*/
public static final String _SYNC_DIRTY = "_sync_dirty";
+
+ /**
+ * The name of the account instance to which this row belongs, which when paired with
+ * {@link #ACCOUNT_TYPE} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_NAME = "account_name";
+
+ /**
+ * The type of account to which this row belongs, which when paired with
+ * {@link #ACCOUNT_NAME} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_TYPE = "account_type";
}
/**
@@ -579,20 +593,6 @@
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
"/event_entities");
- /**
- * The name of the account instance to which this row belongs, which when paired with
- * {@link #ACCOUNT_TYPE} identifies a specific account.
- * <P>Type: TEXT</P>
- */
- public static final String ACCOUNT_NAME = "_sync_account";
-
- /**
- * The type of account to which this row belongs, which when paired with
- * {@link #ACCOUNT_NAME} identifies a specific account.
- * <P>Type: TEXT</P>
- */
- public static final String ACCOUNT_TYPE = "_sync_account_type";
-
public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
return new EntityIteratorImpl(cursor, resolver);
}
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 08ab166..348e9e8 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -629,6 +629,14 @@
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
+ * The permission to access downloads to {@link DESTINATION_EXTERNAL}
+ * which were downloaded by other applications.
+ * @hide
+ */
+ public static final String PERMISSION_SEE_ALL_EXTERNAL =
+ "android.permission.SEE_ALL_EXTERNAL";
+
+ /**
* The content:// URI for the data table in the provider
*/
public static final Uri CONTENT_URI =
@@ -1101,28 +1109,5 @@
* This download doesn't show in the UI or in the notifications.
*/
public static final int VISIBILITY_HIDDEN = 2;
-
- /**
- * Using a file name, create a title for a download. Then store it in
- * the database and return it.
- *
- * @param context Context for reaching the {@link ContentResolver} so
- * the database can be updated with the new title.
- * @param filename Full path to the file. Used to generate a title.
- * @param id Id of the download, so the new title can be stored in the
- * database
- * @return String Newly created title.
- * @hide
- */
- public static String createTitleFromFilename(Context context,
- String filename, long id) {
- if (filename == null) return null;
- String title = new File(filename).getName();
- ContentValues values = new ContentValues();
- values.put(COLUMN_TITLE, title);
- context.getContentResolver().update(ContentUris.withAppendedId(
- CONTENT_URI, id), values, null, null);
- return title;
- }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 23a9f49..bacaf43 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2425,6 +2425,14 @@
public static final String BACKUP_ENABLED = "backup_enabled";
/**
+ * Controls whether application data is automatically restored from backup
+ * at install time.
+ * Type: int ( 0 = disabled, 1 = enabled )
+ * @hide
+ */
+ public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
+
+ /**
* Indicates whether settings backup has been fully provisioned.
* Type: int ( 0 = unprovisioned, 1 = fully provisioned )
* @hide
@@ -2936,6 +2944,13 @@
public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled";
/**
+ * Whether or not a notification is displayed when a Tetherable interface is detected.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String TETHER_NOTIFY = "tether_notify";
+
+ /**
* If nonzero, ANRs in invisible background processes bring up a dialog.
* Otherwise, the process will be silently killed.
* @hide
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 324fbaa..fbc4a81 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -75,8 +75,8 @@
mContext.registerReceiver(mPackageChangedReceiver, packageFilter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mPackageChangedReceiver, sdFilter);
}
@@ -96,8 +96,8 @@
if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
Intent.ACTION_PACKAGE_CHANGED.equals(action) ||
- Intent.ACTION_MEDIA_RESOURCES_AVAILABLE.equals(action) ||
- Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) ||
+ Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
if (DBG) Log.d(TAG, "Got " + action);
// Update list of searchable activities
getSearchables().buildSearchableList();
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 5b48bd2..2a0f986 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -19,12 +19,12 @@
import android.os.Bundle;
/**
- * Listener for speech recognition events, used with RecognitionService.
+ * Listener for speech recognition events, used with RecognitionService.
* This gives you both the final recognition results, as well as various
* intermediate events that can be used to show visual feedback to the user.
* {@hide}
*/
-interface IRecognitionListener {
+oneway interface IRecognitionListener {
/**
* Called when the endpointer is ready for the user to start speaking.
*
@@ -76,4 +76,12 @@
* @param results a Bundle containing the current most likely result.
*/
void onPartialResults(in Bundle results);
+
+ /**
+ * Reserved for adding future events.
+ *
+ * @param eventType the type of the occurred event
+ * @param params a Bundle containing the passed parameters
+ */
+ void onEvent(in int eventType, in Bundle params);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index ca9af15..8b27e63 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -29,7 +29,7 @@
* accessing recognition service.
* {@hide}
*/
-interface IRecognitionService {
+oneway interface IRecognitionService {
/**
* Starts listening for speech. Please note that the recognition service supports
* one listener only, therefore, if this function is called from two different threads,
@@ -38,7 +38,7 @@
* @param recognizerIntent the intent from which the invocation occurred. Additionally,
* this intent can contain extra parameters to manipulate the behavior of the recognition
* client. For more information see {@link RecognizerIntent}.
- * @param listener to receive callbacks
+ * @param listener to receive callbacks, note that this must be non-null
*/
void startListening(in Intent recognizerIntent, in IRecognitionListener listener);
@@ -46,11 +46,15 @@
* Stops listening for speech. Speech captured so far will be recognized as
* if the user had stopped speaking at this point. The function has no effect unless it
* is called during the speech capturing.
+ *
+ * @param listener to receive callbacks, note that this must be non-null
*/
- void stopListening();
+ void stopListening(in IRecognitionListener listener);
/**
* Cancels the speech recognition.
+ *
+ * @param listener to receive callbacks, note that this must be non-null
*/
- void cancel();
+ void cancel(in IRecognitionListener listener);
}
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
index eab3f40..5434887 100644
--- a/core/java/android/speech/RecognitionListener.java
+++ b/core/java/android/speech/RecognitionListener.java
@@ -24,12 +24,6 @@
* Application main thread.
*/
public interface RecognitionListener {
-
- /**
- * Called when RecognitionManager is successfully initialized
- */
- void onInit();
-
/**
* Called when the endpointer is ready for the user to start speaking.
*
@@ -76,7 +70,7 @@
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
- * {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
void onResults(Bundle results);
@@ -89,8 +83,15 @@
*
* @param partialResults the returned results. To retrieve the results in
* ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
- * {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
void onPartialResults(Bundle partialResults);
+ /**
+ * Reserved for adding future events.
+ *
+ * @param eventType the type of the occurred event
+ * @param params a Bundle containing the passed parameters
+ */
+ void onEvent(int eventType, Bundle params);
}
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
index 79ae480..7915208 100644
--- a/core/java/android/speech/RecognitionManager.java
+++ b/core/java/android/speech/RecognitionManager.java
@@ -22,17 +22,23 @@
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
/**
* This class provides access to the speech recognition service. This service allows access to the
* speech recognizer. Do not instantiate this class directly, instead, call
- * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}. This
- * class is not thread safe and must be synchronized externally if accessed from multiple threads.
+ * {@link RecognitionManager#createRecognitionManager(Context)}. This class's methods must be
+ * invoked only from the main application thread. Please note that the application must have
+ * {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class.
*/
public class RecognitionManager {
/** DEBUG value to enable verbose debug prints */
@@ -47,8 +53,40 @@
* {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
* recognition results, where the first element is the most likely candidate.
*/
- public static final String RECOGNITION_RESULTS_STRING_ARRAY =
- "recognition_results_string_array";
+ public static final String RESULTS_RECOGNITION = "results_recognition";
+
+ /** Network operation timed out. */
+ public static final int ERROR_NETWORK_TIMEOUT = 1;
+
+ /** Other network related errors. */
+ public static final int ERROR_NETWORK = 2;
+
+ /** Audio recording error. */
+ public static final int ERROR_AUDIO = 3;
+
+ /** Server sends error status. */
+ public static final int ERROR_SERVER = 4;
+
+ /** Other client side errors. */
+ public static final int ERROR_CLIENT = 5;
+
+ /** No speech input */
+ public static final int ERROR_SPEECH_TIMEOUT = 6;
+
+ /** No recognition result matched. */
+ public static final int ERROR_NO_MATCH = 7;
+
+ /** RecognitionService busy. */
+ public static final int ERROR_RECOGNIZER_BUSY = 8;
+
+ /** Insufficient permissions */
+ public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
+
+ /** action codes */
+ private final static int MSG_START = 1;
+ private final static int MSG_STOP = 2;
+ private final static int MSG_CANCEL = 3;
+ private final static int MSG_CHANGE_LISTENER = 4;
/** The actual RecognitionService endpoint */
private IRecognitionService mService;
@@ -59,80 +97,74 @@
/** Context with which the manager was created */
private final Context mContext;
- /** Listener that will receive all the callbacks */
- private final RecognitionListener mListener;
-
- /** Helper class wrapping the IRecognitionListener */
- private final InternalRecognitionListener mInternalRecognitionListener;
-
- /** Network operation timed out. */
- public static final int NETWORK_TIMEOUT_ERROR = 1;
-
- /** Other network related errors. */
- public static final int NETWORK_ERROR = 2;
-
- /** Audio recording error. */
- public static final int AUDIO_ERROR = 3;
-
- /** Server sends error status. */
- public static final int SERVER_ERROR = 4;
-
- /** Other client side errors. */
- public static final int CLIENT_ERROR = 5;
-
- /** No speech input */
- public static final int SPEECH_TIMEOUT_ERROR = 6;
-
- /** No recognition result matched. */
- public static final int NO_MATCH_ERROR = 7;
-
- /** RecognitionService busy. */
- public static final int SERVER_BUSY_ERROR = 8;
+ /** Handler that will execute the main tasks */
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START:
+ handleStartListening((Intent) msg.obj);
+ break;
+ case MSG_STOP:
+ handleStopMessage();
+ break;
+ case MSG_CANCEL:
+ handleCancelMessage();
+ break;
+ case MSG_CHANGE_LISTENER:
+ handleChangeListener((RecognitionListener) msg.obj);
+ break;
+ }
+ }
+ };
/**
- * RecognitionManager was not initialized yet, most probably because
- * {@link RecognitionListener#onInit()} was not called yet.
+ * Temporary queue, saving the messages until the connection will be established, afterwards,
+ * only mHandler will receive the messages
*/
- public static final int MANAGER_NOT_INITIALIZED_ERROR = 9;
+ private final Queue<Message> mPendingTasks = new LinkedList<Message>();
+
+ /** The Listener that will receive all the callbacks */
+ private final InternalListener mListener = new InternalListener();
/**
- * The right way to create a RecognitionManager is by using
+ * The right way to create a {@code RecognitionManager} is by using
* {@link #createRecognitionManager} static factory method
*/
- private RecognitionManager(final RecognitionListener listener, final Context context) {
- mInternalRecognitionListener = new InternalRecognitionListener();
+ private RecognitionManager(final Context context) {
mContext = context;
- mListener = listener;
}
/**
- * Basic ServiceConnection which just records mService variable.
+ * Basic ServiceConnection that records the mService variable. Additionally, on creation it
+ * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
*/
private class Connection implements ServiceConnection {
- public synchronized void onServiceConnected(final ComponentName name,
- final IBinder service) {
+ public void onServiceConnected(final ComponentName name, final IBinder service) {
+ // always done on the application main thread, so no need to send message to mHandler
mService = IRecognitionService.Stub.asInterface(service);
- if (mListener != null) {
- mListener.onInit();
- }
if (DBG) Log.d(TAG, "onServiceConnected - Success");
+ while (!mPendingTasks.isEmpty()) {
+ mHandler.sendMessage(mPendingTasks.poll());
+ }
}
public void onServiceDisconnected(final ComponentName name) {
+ // always done on the application main thread, so no need to send message to mHandler
mService = null;
mConnection = null;
+ mPendingTasks.clear();
if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
}
}
/**
* Checks whether a speech recognition service is available on the system. If this method
- * returns {@code false},
- * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}
- * will fail.
+ * returns {@code false}, {@link RecognitionManager#createRecognitionManager(Context)} will
+ * fail.
*
- * @param context with which RecognitionManager will be created
+ * @param context with which {@code RecognitionManager} will be created
* @return {@code true} if recognition is available, {@code false} otherwise
*/
public static boolean isRecognitionAvailable(final Context context) {
@@ -142,78 +174,60 @@
}
/**
- * Factory method to create a new RecognitionManager
+ * Factory method to create a new {@code RecognitionManager}, please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called before dispatching any
+ * command to the created {@code RecognitionManager}.
*
- * @param context in which to create RecognitionManager
- * @param listener that will receive all the callbacks from the created
- * {@link RecognitionManager}
- * @param recognizerIntent contains initialization parameters for the speech recognizer. The
- * intent action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. Future
- * versions of this API may add startup parameters for speech recognizer.
- * @return null if a recognition service implementation is not installed or if speech
- * recognition is not supported by the device, otherwise a new RecognitionManager is
- * returned. The created RecognitionManager can only be used after the
- * {@link RecognitionListener#onInit()} method has been called.
+ * @param context in which to create {@code RecognitionManager}
+ * @return a new {@code RecognitionManager}
*/
- public static RecognitionManager createRecognitionManager(final Context context,
- final RecognitionListener listener, final Intent recognizerIntent) {
- if (context == null || recognizerIntent == null) {
- throw new IllegalArgumentException(
- "Context and recognizerListener argument cannot be null)");
+ public static RecognitionManager createRecognitionManager(final Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("Context cannot be null)");
}
- RecognitionManager manager = new RecognitionManager(listener, context);
- manager.mConnection = manager.new Connection();
- if (!context.bindService(recognizerIntent, manager.mConnection, Context.BIND_AUTO_CREATE)) {
- Log.e(TAG, "bind to recognition service failed");
- listener.onError(CLIENT_ERROR);
- return null;
- }
- return manager;
+ checkIsCalledFromMainThread();
+ return new RecognitionManager(context);
}
/**
- * Checks whether the service is connected
- *
- * @param functionName from which the call originated
- * @return {@code true} if the service was successfully initialized, {@code false} otherwise
+ * Sets the listener that will receive all the callbacks. The previous unfinished commands will
+ * be executed with the old listener, while any following command will be executed with the new
+ * listener.
+ *
+ * @param listener listener that will receive all the callbacks from the created
+ * {@link RecognitionManager}, this must not be null.
*/
- private boolean connectToService(final String functionName) {
- if (mService != null) {
- return true;
- }
- if (mConnection == null) {
- if (DBG) Log.d(TAG, "restarting connection to the recognition service");
- mConnection = new Connection();
- mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), mConnection,
- Context.BIND_AUTO_CREATE);
- }
- mInternalRecognitionListener.onError(MANAGER_NOT_INITIALIZED_ERROR);
- Log.e(TAG, functionName + " was called before service connection was initialized");
- return false;
+ public void setRecognitionListener(RecognitionListener listener) {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
}
/**
- * Starts listening for speech.
+ * Starts listening for speech. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
+ * {@link RuntimeException} will be thrown.
*
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
- * action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. The intent may also
- * contain optional extras, see {@link RecognizerIntent}. If these values are not set
- * explicitly, default values will be used by the recognizer.
+ * may also contain optional extras, see {@link RecognizerIntent}. If these values are
+ * not set explicitly, default values will be used by the recognizer.
*/
- public void startListening(Intent recognizerIntent) {
+ public void startListening(final Intent recognizerIntent) {
if (recognizerIntent == null) {
- throw new IllegalArgumentException("recognizerIntent argument cannot be null");
+ throw new IllegalArgumentException("intent must not be null");
}
- if (!connectToService("startListening")) {
- return; // service is not connected yet, reconnect in progress
+ checkIsCalledFromMainThread();
+ if (mConnection == null) { // first time connection
+ mConnection = new Connection();
+ if (!mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
+ mConnection, Context.BIND_AUTO_CREATE)) {
+ Log.e(TAG, "bind to recognition service failed");
+ mConnection = null;
+ mService = null;
+ mListener.onError(ERROR_CLIENT);
+ return;
+ }
}
- try {
- mService.startListening(recognizerIntent, mInternalRecognitionListener);
- if (DBG) Log.d(TAG, "service start listening command succeded");
- } catch (final RemoteException e) {
- Log.e(TAG, "startListening() failed", e);
- mInternalRecognitionListener.onError(CLIENT_ERROR);
- }
+ putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
}
/**
@@ -222,100 +236,200 @@
* called, as the speech endpointer will automatically stop the recognizer listening when it
* determines speech has completed. However, you can manipulate endpointer parameters directly
* using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
- * want to manually call this method to stop listening sooner.
+ * want to manually call this method to stop listening sooner. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
+ * {@link RuntimeException} will be thrown.
*/
public void stopListening() {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_STOP));
+ }
+
+ /**
+ * Cancels the speech recognition. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
+ * {@link RuntimeException} will be thrown.
+ */
+ public void cancel() {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_CANCEL));
+ }
+
+ private static void checkIsCalledFromMainThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new RuntimeException(
+ "RecognitionManager should be used only from the application's main thread");
+ }
+ }
+
+ private void putMessage(Message msg) {
if (mService == null) {
- return; // service is not connected, but no need to reconnect at this point
+ mPendingTasks.offer(msg);
+ } else {
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /** sends the actual message to the service */
+ private void handleStartListening(Intent recognizerIntent) {
+ if (!checkOpenConnection()) {
+ return;
}
try {
- mService.stopListening();
+ mService.startListening(recognizerIntent, mListener);
+ if (DBG) Log.d(TAG, "service start listening command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "startListening() failed", e);
+ mListener.onError(ERROR_CLIENT);
+ }
+ }
+
+ /** sends the actual message to the service */
+ private void handleStopMessage() {
+ if (!checkOpenConnection()) {
+ return;
+ }
+ try {
+ mService.stopListening(mListener);
if (DBG) Log.d(TAG, "service stop listening command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "stopListening() failed", e);
- mInternalRecognitionListener.onError(CLIENT_ERROR);
+ mListener.onError(ERROR_CLIENT);
}
}
- /**
- * Cancels the speech recognition.
- */
- public void cancel() {
- if (mService == null) {
- return; // service is not connected, but no need to reconnect at this point
+ /** sends the actual message to the service */
+ private void handleCancelMessage() {
+ if (!checkOpenConnection()) {
+ return;
}
try {
- mService.cancel();
+ mService.cancel(mListener);
if (DBG) Log.d(TAG, "service cancel command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "cancel() failed", e);
- mInternalRecognitionListener.onError(CLIENT_ERROR);
+ mListener.onError(ERROR_CLIENT);
}
}
+
+ private boolean checkOpenConnection() {
+ if (mService != null) {
+ return true;
+ }
+ mListener.onError(ERROR_CLIENT);
+ Log.e(TAG, "not connected to the recognition service");
+ return false;
+ }
+
+ /** changes the listener */
+ private void handleChangeListener(RecognitionListener listener) {
+ if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
+ mListener.mInternalListener = listener;
+ }
/**
- * Destroys the RecognitionManager object. Note that after calling this method all method calls
- * on this object will fail, triggering {@link RecognitionListener#onError}.
+ * Destroys the {@code RecognitionManager} object. Note that after calling this method all
+ * method calls on this object will fail, triggering {@link RecognitionListener#onError}.
*/
public void destroy() {
if (mConnection != null) {
mContext.unbindService(mConnection);
}
+ mPendingTasks.clear();
mService = null;
+ mConnection = null;
}
/**
- * Internal wrapper of IRecognitionListener which will propagate the results
- * to RecognitionListener
+ * Internal wrapper of IRecognitionListener which will propagate the results to
+ * RecognitionListener
*/
- private class InternalRecognitionListener extends IRecognitionListener.Stub {
+ private class InternalListener extends IRecognitionListener.Stub {
+ private RecognitionListener mInternalListener;
+
+ private final static int MSG_BEGINNING_OF_SPEECH = 1;
+ private final static int MSG_BUFFER_RECEIVED = 2;
+ private final static int MSG_END_OF_SPEECH = 3;
+ private final static int MSG_ERROR = 4;
+ private final static int MSG_READY_FOR_SPEECH = 5;
+ private final static int MSG_RESULTS = 6;
+ private final static int MSG_PARTIAL_RESULTS = 7;
+ private final static int MSG_RMS_CHANGED = 8;
+ private final static int MSG_ON_EVENT = 9;
+
+ private final Handler mInternalHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mInternalListener == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_BEGINNING_OF_SPEECH:
+ mInternalListener.onBeginningOfSpeech();
+ break;
+ case MSG_BUFFER_RECEIVED:
+ mInternalListener.onBufferReceived((byte[]) msg.obj);
+ break;
+ case MSG_END_OF_SPEECH:
+ mInternalListener.onEndOfSpeech();
+ break;
+ case MSG_ERROR:
+ mInternalListener.onError((Integer) msg.obj);
+ break;
+ case MSG_READY_FOR_SPEECH:
+ mInternalListener.onReadyForSpeech((Bundle) msg.obj);
+ break;
+ case MSG_RESULTS:
+ mInternalListener.onResults((Bundle) msg.obj);
+ break;
+ case MSG_PARTIAL_RESULTS:
+ mInternalListener.onPartialResults((Bundle) msg.obj);
+ break;
+ case MSG_RMS_CHANGED:
+ mInternalListener.onRmsChanged((Float) msg.obj);
+ break;
+ case MSG_ON_EVENT:
+ mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
+ break;
+ }
+ }
+ };
public void onBeginningOfSpeech() {
- if (mListener != null) {
- mListener.onBeginningOfSpeech();
- }
+ Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
}
public void onBufferReceived(final byte[] buffer) {
- if (mListener != null) {
- mListener.onBufferReceived(buffer);
- }
+ Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
}
public void onEndOfSpeech() {
- if (mListener != null) {
- mListener.onEndOfSpeech();
- }
+ Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
}
public void onError(final int error) {
- if (mListener != null) {
- mListener.onError(error);
- }
+ Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
}
public void onReadyForSpeech(final Bundle noiseParams) {
- if (mListener != null) {
- mListener.onReadyForSpeech(noiseParams);
- }
+ Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
}
public void onResults(final Bundle results) {
- if (mListener != null) {
- mListener.onResults(results);
- }
+ Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
}
public void onPartialResults(final Bundle results) {
- if (mListener != null) {
- mListener.onPartialResults(results);
- }
+ Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
}
public void onRmsChanged(final float rmsdB) {
- if (mListener != null) {
- mListener.onRmsChanged(rmsdB);
- }
+ Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
+ }
+
+ public void onEvent(final int eventType, final Bundle params) {
+ Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
+ .sendToTarget();
}
}
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
new file mode 100644
index 0000000..0d74960
--- /dev/null
+++ b/core/java/android/speech/RecognitionService.java
@@ -0,0 +1,295 @@
+/*
+ * 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.speech;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides a base class for recognition service implementations. This class should be
+ * extended only in case you wish to implement a new speech recognizer. Please not that the
+ * implementation of this service is state-less.
+ */
+public abstract class RecognitionService extends Service {
+
+ /** Log messages identifier */
+ private static final String TAG = "RecognitionService";
+
+ /** Debugging flag */
+ private static final boolean DBG = false;
+
+ /**
+ * The current callback of an application that invoked the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method
+ */
+ private Callback mCurrentCallback = null;
+
+ private static final int MSG_START_LISTENING = 1;
+
+ private static final int MSG_STOP_LISTENING = 2;
+
+ private static final int MSG_CANCEL = 3;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_LISTENING:
+ StartListeningArgs args = (StartListeningArgs) msg.obj;
+ dispatchStartListening(args.mIntent, args.mListener);
+ break;
+ case MSG_STOP_LISTENING:
+ dispatchStopListening((IRecognitionListener) msg.obj);
+ break;
+ case MSG_CANCEL:
+ dispatchCancel((IRecognitionListener) msg.obj);
+ }
+ }
+ };
+
+ private void dispatchStartListening(Intent intent, IRecognitionListener listener) {
+ if (mCurrentCallback == null) {
+ if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
+ mCurrentCallback = new Callback(listener);
+ RecognitionService.this.onStartListening(intent, mCurrentCallback);
+ } else {
+ try {
+ listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
+ } catch (RemoteException e) {
+ Log.d(TAG, "onError call from startListening failed");
+ }
+ Log.i(TAG, "concurrent startListening received - ignoring this call");
+ }
+ }
+
+ private void dispatchStopListening(IRecognitionListener listener) {
+ try {
+ if (mCurrentCallback == null) {
+ listener.onError(RecognitionManager.ERROR_CLIENT);
+ Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
+ } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
+ listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
+ Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
+ } else { // the correct state
+ RecognitionService.this.onStopListening(mCurrentCallback);
+ }
+ } catch (RemoteException e) { // occurs if onError fails
+ Log.d(TAG, "onError call from stopListening failed");
+ }
+ }
+
+ private void dispatchCancel(IRecognitionListener listener) {
+ if (mCurrentCallback == null) {
+ Log.w(TAG, "cancel called with no preceding startListening - ignoring");
+ } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
+ Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
+ } else { // the correct state
+ RecognitionService.this.onCancel(mCurrentCallback);
+ mCurrentCallback = null;
+ if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
+ }
+ }
+
+ private class StartListeningArgs {
+ public final Intent mIntent;
+
+ public final IRecognitionListener mListener;
+
+ public StartListeningArgs(Intent intent, IRecognitionListener listener) {
+ this.mIntent = intent;
+ this.mListener = listener;
+ }
+ }
+
+ /** Binder of the recognition service */
+ private final IRecognitionService.Stub mBinder = new IRecognitionService.Stub() {
+ public void startListening(Intent recognizerIntent, IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
+ if (checkPermissions(listener)) {
+ mHandler.sendMessage(Message.obtain(mHandler, MSG_START_LISTENING,
+ new StartListeningArgs(recognizerIntent, listener)));
+ }
+ }
+
+ public void stopListening(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
+ if (checkPermissions(listener)) {
+ mHandler.sendMessage(Message.obtain(mHandler, MSG_STOP_LISTENING, listener));
+ }
+ }
+
+ public void cancel(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
+ if (checkPermissions(listener)) {
+ mHandler.sendMessage(Message.obtain(mHandler, MSG_CANCEL, listener));
+ }
+ }
+ };
+
+ /**
+ * Checks whether the caller has sufficient permissions
+ *
+ * @param listener to send the error message to in case of error
+ * @return {@code true} if the caller has enough permissions, {@code false} otherwise
+ */
+ private boolean checkPermissions(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "checkPermissions");
+ if (RecognitionService.this.checkCallingOrSelfPermission(android.Manifest.permission.
+ RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ try {
+ Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions");
+ listener.onError(RecognitionManager.ERROR_INSUFFICIENT_PERMISSIONS);
+ } catch (RemoteException re) {
+ Log.e(TAG, "sending ERROR_INSUFFICIENT_PERMISSIONS message failed", re);
+ }
+ return false;
+ }
+
+ /**
+ * Notifies the service that it should start listening for speech.
+ *
+ * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+ * may also contain optional extras, see {@link RecognizerIntent}. If these values are
+ * not set explicitly, default values should be used by the recognizer.
+ * @param listener that will receive the service's callbacks
+ */
+ protected abstract void onStartListening(Intent recognizerIntent, Callback listener);
+
+ /**
+ * Notifies the service that it should cancel the speech recognition.
+ */
+ protected abstract void onCancel(Callback listener);
+
+ /**
+ * Notifies the service that it should stop listening for speech. Speech captured so far should
+ * be recognized as if the user had stopped speaking at this point. This method is only called
+ * if the application calls it explicitly.
+ */
+ protected abstract void onStopListening(Callback listener);
+
+ @Override
+ public final IBinder onBind(final Intent intent) {
+ if (DBG) Log.d(TAG, "onBind, intent=" + intent);
+ return mBinder;
+ }
+
+ /**
+ * This class receives callbacks from the speech recognition service and forwards them to the
+ * user. An instance of this class is passed to the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
+ * these methods on any thread.
+ */
+ public class Callback {
+ private final IRecognitionListener mListener;
+
+ private Callback(IRecognitionListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * The service should call this method when the user has started to speak.
+ */
+ public void beginningOfSpeech() throws RemoteException {
+ if (DBG) Log.d(TAG, "beginningOfSpeech");
+ mListener.onBeginningOfSpeech();
+ }
+
+ /**
+ * The service should call this method when sound has been received. The purpose of this
+ * function is to allow giving feedback to the user regarding the captured audio.
+ *
+ * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
+ * single channel audio stream. The sample rate is implementation dependent.
+ */
+ public void bufferReceived(byte[] buffer) throws RemoteException {
+ mListener.onBufferReceived(buffer);
+ }
+
+ /**
+ * The service should call this method after the user stops speaking.
+ */
+ public void endOfSpeech() throws RemoteException {
+ mListener.onEndOfSpeech();
+ }
+
+ /**
+ * The service should call this method when a network or recognition error occurred.
+ *
+ * @param error code is defined in {@link RecognitionManager}
+ */
+ public void error(int error) throws RemoteException {
+ mCurrentCallback = null;
+ mListener.onError(error);
+ }
+
+ /**
+ * The service should call this method when partial recognition results are available. This
+ * method can be called at any time between {@link #beginningOfSpeech()} and
+ * {@link #results(Bundle)} when partial results are ready. This method may be called zero,
+ * one or multiple times for each call to {@link RecognitionManager#startListening(Intent)},
+ * depending on the speech recognition service implementation.
+ *
+ * @param partialResults the returned results. To retrieve the results in
+ * ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
+ */
+ public void partialResults(Bundle partialResults) throws RemoteException {
+ mListener.onPartialResults(partialResults);
+ }
+
+ /**
+ * The service should call this method when the endpointer is ready for the user to start
+ * speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ public void readyForSpeech(Bundle params) throws RemoteException {
+ mListener.onReadyForSpeech(params);
+ }
+
+ /**
+ * The service should call this method when recognition results are ready.
+ *
+ * @param results the recognition results. To retrieve the results in {@code
+ * ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
+ */
+ public void results(Bundle results) throws RemoteException {
+ mCurrentCallback = null;
+ mListener.onResults(results);
+ }
+
+ /**
+ * The service should call this method when the sound level in the audio stream has changed.
+ * There is no guarantee that this method will be called.
+ *
+ * @param rmsdB the new RMS dB value
+ */
+ public void rmsChanged(float rmsdB) throws RemoteException {
+ mListener.onRmsChanged(rmsdB);
+ }
+ }
+}
diff --git a/core/java/android/storage/StorageEventListener.java b/core/java/android/storage/StorageEventListener.java
deleted file mode 100644
index cd71090..0000000
--- a/core/java/android/storage/StorageEventListener.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.
- */
-
-package android.storage;
-
-/**
- * Used for receiving notifications from the StorageManager
- */
-public interface StorageEventListener {
- /**
- * Called when the ability to share a volume has changed.
- * @param method the share-method which has changed.
- * @param available true if the share is available.
- */
- public void onShareAvailabilityChanged(String method, boolean available);
-
- /**
- * Called when media has been inserted
- * @param label the system defined label for the volume.
- * @param path the filesystem path for the volume.
- * @param major the major number of the device.
- * @param minor the minor number of the device.
- */
- public void onMediaInserted(String label, String path, int major, int minor);
-
- /**
- * Called when media has been removed
- * @param label the system defined label for the volume.
- * @param path the filesystem path for the volume.
- * @param major the major number of the device.
- * @param minor the minor number of the device.
- * @param clean the media was removed cleanly.
- */
- public void onMediaRemoved(String label, String path, int major, int minor, boolean clean);
-
- /**
- * Called when a volume has changed state
- * @param label the system defined label for the volume.
- * @param path the filesystem path for the volume.
- * @param oldState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
- * @param newState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
- */
- public void onVolumeStateChanged(String label, String path, String oldState, String newState);
-}
diff --git a/core/java/android/storage/StorageManager.java b/core/java/android/storage/StorageManager.java
deleted file mode 100644
index dd8bd63..0000000
--- a/core/java/android/storage/StorageManager.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * 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.
- */
-
-package android.storage;
-
-import android.content.Context;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.Parcelable;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ServiceManager;
-import android.os.IMountService;
-import android.os.IMountServiceListener;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Class that lets you access the device's storage management functions. Get an instance of this
- * class by calling {@link android.content.Context#getSystemService(java.lang.String)
- * Context.getSystemService()} with an argument of {@link android.content.Context#STORAGE_SERVICE}.
- */
-public class StorageManager
-{
- private static final String TAG = "StorageManager";
-
- /*
- * Our internal MountService binder reference
- */
- private IMountService mMountService;
-
- /*
- * The looper target for callbacks
- */
- Looper mTgtLooper;
-
- /*
- * Target listener for binder callbacks
- */
- private MountServiceBinderListener mBinderListener;
-
- /*
- * *static* list of our listeners
- */
- static final ArrayList<ListenerDelegate> sListeners = new ArrayList<ListenerDelegate>();
-
- private class MountServiceBinderListener extends IMountServiceListener.Stub {
- public void onShareAvailabilityChanged(String method, boolean available) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendShareAvailabilityChanged(method, available);
- }
- }
-
- public void onMediaInserted(String label, String path, int major, int minor) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendMediaInserted(label, path, major, minor);
- }
- }
-
- public void onMediaRemoved(String label, String path, int major, int minor, boolean clean) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendMediaRemoved(label, path, major, minor, clean);
- }
- }
-
- public void onVolumeStateChanged(String label, String path, String oldState, String newState) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendVolumeStateChanged(label, path, oldState, newState);
- }
- }
- }
-
- /**
- * Private base class for messages sent between the callback thread
- * and the target looper handler
- */
- private class StorageEvent {
- public static final int EVENT_SHARE_AVAILABILITY_CHANGED = 1;
- public static final int EVENT_MEDIA_INSERTED = 2;
- public static final int EVENT_MEDIA_REMOVED = 3;
- public static final int EVENT_VOLUME_STATE_CHANGED = 4;
-
- private Message mMessage;
-
- public StorageEvent(int what) {
- mMessage = Message.obtain();
- mMessage.what = what;
- mMessage.obj = this;
- }
-
- public Message getMessage() {
- return mMessage;
- }
- }
-
- /**
- * Message sent on a share availability change.
- */
- private class ShareAvailabilityChangedStorageEvent extends StorageEvent {
- public String method;
- public boolean available;
-
- public ShareAvailabilityChangedStorageEvent(String m, boolean a) {
- super(EVENT_SHARE_AVAILABILITY_CHANGED);
- method = m;
- available = a;
- }
- }
-
- /**
- * Message sent on media insertion
- */
- private class MediaInsertedStorageEvent extends StorageEvent {
- public String label;
- public String path;
- public int major;
- public int minor;
-
- public MediaInsertedStorageEvent(String l, String p, int maj, int min) {
- super(EVENT_MEDIA_INSERTED);
- label = l;
- path = p;
- major = maj;
- minor = min;
- }
- }
-
- /**
- * Message sent on media removal
- */
- private class MediaRemovedStorageEvent extends StorageEvent {
- public String label;
- public String path;
- public int major;
- public int minor;
- public boolean clean;
-
- public MediaRemovedStorageEvent(String l, String p, int maj, int min, boolean c) {
- super(EVENT_MEDIA_REMOVED);
- label = l;
- path = p;
- major = maj;
- minor = min;
- clean = c;
- }
- }
-
- /**
- * Message sent on volume state change
- */
- private class VolumeStateChangedStorageEvent extends StorageEvent {
- public String label;
- public String path;
- public String oldState;
- public String newState;
-
- public VolumeStateChangedStorageEvent(String l, String p, String oldS, String newS) {
- super(EVENT_VOLUME_STATE_CHANGED);
- label = l;
- path = p;
- oldState = oldS;
- newState = newS;
- }
- }
-
- /**
- * Private class containing sender and receiver code for StorageEvents
- */
- private class ListenerDelegate {
- final StorageEventListener mStorageEventListener;
- private final Handler mHandler;
-
- ListenerDelegate(StorageEventListener listener) {
- mStorageEventListener = listener;
- mHandler = new Handler(mTgtLooper) {
- @Override
- public void handleMessage(Message msg) {
- StorageEvent e = (StorageEvent) msg.obj;
-
- if (msg.what == StorageEvent.EVENT_SHARE_AVAILABILITY_CHANGED) {
- ShareAvailabilityChangedStorageEvent ev = (ShareAvailabilityChangedStorageEvent) e;
- mStorageEventListener.onShareAvailabilityChanged(ev.method, ev.available);
- } else if (msg.what == StorageEvent.EVENT_MEDIA_INSERTED) {
- MediaInsertedStorageEvent ev = (MediaInsertedStorageEvent) e;
- mStorageEventListener.onMediaInserted(ev.label, ev.path, ev.major, ev.minor);
- } else if (msg.what == StorageEvent.EVENT_MEDIA_REMOVED) {
- MediaRemovedStorageEvent ev = (MediaRemovedStorageEvent) e;
- mStorageEventListener.onMediaRemoved(ev.label, ev.path, ev.major, ev.minor, ev.clean);
- } else if (msg.what == StorageEvent.EVENT_VOLUME_STATE_CHANGED) {
- VolumeStateChangedStorageEvent ev = (VolumeStateChangedStorageEvent) e;
- mStorageEventListener.onVolumeStateChanged(ev.label, ev.path, ev.oldState, ev.newState);
- } else {
- Log.e(TAG, "Unsupported event " + msg.what);
- }
- }
- };
- }
-
- StorageEventListener getListener() {
- return mStorageEventListener;
- }
-
- void sendShareAvailabilityChanged(String method, boolean available) {
- ShareAvailabilityChangedStorageEvent e = new ShareAvailabilityChangedStorageEvent(method, available);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendMediaInserted(String label, String path, int major, int minor) {
- MediaInsertedStorageEvent e = new MediaInsertedStorageEvent(label, path, major, minor);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendMediaRemoved(String label, String path, int major, int minor, boolean clean) {
- MediaRemovedStorageEvent e = new MediaRemovedStorageEvent(label, path, major, minor, clean);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendVolumeStateChanged(String label, String path, String oldState, String newState) {
- VolumeStateChangedStorageEvent e = new VolumeStateChangedStorageEvent(label, path, oldState, newState);
- mHandler.sendMessage(e.getMessage());
- }
- }
-
- /**
- * {@hide}
- */
- public StorageManager(Looper tgtLooper) throws RemoteException {
- mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
- mTgtLooper = tgtLooper;
- mBinderListener = new MountServiceBinderListener();
- mMountService.registerListener(mBinderListener);
- }
-
-
- /**
- * Registers a {@link android.storage.StorageEventListener StorageEventListener}.
- *
- * @param listener A {@link android.storage.StorageEventListener StorageEventListener} object.
- *
- */
- public void registerListener(StorageEventListener listener) {
- if (listener == null) {
- return;
- }
-
- synchronized (sListeners) {
- sListeners.add(new ListenerDelegate(listener));
- }
- }
-
- /**
- * Unregisters a {@link android.storage.StorageEventListener StorageEventListener}.
- *
- * @param listener A {@link android.storage.StorageEventListener StorageEventListener} object.
- *
- */
- public void unregisterListener(StorageEventListener listener) {
- if (listener == null) {
- return;
- }
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- sListeners.remove(i);
- break;
- }
- }
- }
- }
-}
diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java
index cb39f7d..9d8bfd9 100644
--- a/core/java/android/text/util/Rfc822Tokenizer.java
+++ b/core/java/android/text/util/Rfc822Tokenizer.java
@@ -41,7 +41,6 @@
* It will try to be tolerant of broken syntax instead of
* returning an error.
*
- * @hide
*/
public static void tokenize(CharSequence text, Collection<Rfc822Token> out) {
StringBuilder name = new StringBuilder();
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 094b7dd..07b2d1c 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -98,6 +98,9 @@
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
+ static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>();
+ static boolean sFirstDrawComplete = false;
+
private static int sDrawTime;
long mLastTrackballTime = 0;
@@ -254,6 +257,14 @@
return sInstanceCount;
}
+ public static void addFirstDrawHandler(Runnable callback) {
+ synchronized (sFirstDrawHandlers) {
+ if (!sFirstDrawComplete) {
+ sFirstDrawHandlers.add(callback);
+ }
+ }
+ }
+
// FIXME for perf testing only
private boolean mProfile = false;
@@ -1189,6 +1200,15 @@
return;
}
+ if (!sFirstDrawComplete) {
+ synchronized (sFirstDrawHandlers) {
+ sFirstDrawComplete = true;
+ for (int i=0; i<sFirstDrawHandlers.size(); i++) {
+ post(sFirstDrawHandlers.get(i));
+ }
+ }
+ }
+
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index c8396c4..000e4ce 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -880,7 +880,7 @@
region.inset(-1.0f, -1.0f);
if (mFillBefore) {
final Transformation previousTransformation = mPreviousTransformation;
- applyTransformation(0.0f, previousTransformation);
+ applyTransformation(mInterpolator.getInterpolation(0.0f), previousTransformation);
}
}
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 98b2594..1546dcd 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -282,7 +282,9 @@
final Animation a = animations.get(i);
temp.clear();
- a.applyTransformation(0.0f, temp);
+ final Interpolator interpolator = a.mInterpolator;
+ a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f)
+ : 0.0f, temp);
previousTransformation.compose(temp);
}
}
diff --git a/core/java/android/webkit/CacheLoader.java b/core/java/android/webkit/CacheLoader.java
index de8f888..aeb537c 100644
--- a/core/java/android/webkit/CacheLoader.java
+++ b/core/java/android/webkit/CacheLoader.java
@@ -43,7 +43,7 @@
protected boolean setupStreamAndSendStatus() {
mDataStream = mCacheResult.inStream;
mContentLength = mCacheResult.contentLength;
- mHandler.status(1, 1, mCacheResult.httpStatusCode, "OK");
+ mLoadListener.status(1, 1, mCacheResult.httpStatusCode, "OK");
return true;
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 6790c5d..61a2d2ef 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -651,7 +651,42 @@
String message = msg.getData().getString("message");
String sourceID = msg.getData().getString("sourceID");
int lineNumber = msg.getData().getInt("lineNumber");
- mWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
+ int msgLevel = msg.getData().getInt("msgLevel");
+ int numberOfMessageLevels = ConsoleMessage.MessageLevel.values().length;
+ // Sanity bounds check as we'll index an array with msgLevel
+ if (msgLevel < 0 || msgLevel >= numberOfMessageLevels) {
+ msgLevel = 0;
+ }
+
+ ConsoleMessage.MessageLevel messageLevel =
+ ConsoleMessage.MessageLevel.values()[msgLevel];
+
+ if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
+ lineNumber, messageLevel))) {
+ // If false was returned the user did not provide their own console function so
+ // we should output some default messages to the system log.
+ String logTag = "Web Console";
+ String logMessage = message + " at " + sourceID + ":" + lineNumber;
+
+ switch (messageLevel) {
+ case TIP:
+ Log.v(logTag, logMessage);
+ break;
+ case LOG:
+ Log.i(logTag, logMessage);
+ break;
+ case WARNING:
+ Log.w(logTag, logMessage);
+ break;
+ case ERROR:
+ Log.e(logTag, logMessage);
+ break;
+ case DEBUG:
+ Log.d(logTag, logMessage);
+ break;
+ }
+ }
+
break;
case GET_VISITED_HISTORY:
@@ -1286,8 +1321,10 @@
* occurred.
* @param sourceID The filename of the source file in which the error
* occurred.
+ * @param msgLevel The message level, corresponding to the MessageLevel enum in
+ * WebCore/page/Console.h
*/
- public void addMessageToConsole(String message, int lineNumber, String sourceID) {
+ public void addMessageToConsole(String message, int lineNumber, String sourceID, int msgLevel) {
if (mWebChromeClient == null) {
return;
}
@@ -1296,6 +1333,7 @@
msg.getData().putString("message", message);
msg.getData().putString("sourceID", sourceID);
msg.getData().putInt("lineNumber", lineNumber);
+ msg.getData().putInt("msgLevel", msgLevel);
sendMessage(msg);
}
diff --git a/core/java/android/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java
new file mode 100644
index 0000000..a9c351a
--- /dev/null
+++ b/core/java/android/webkit/ConsoleMessage.java
@@ -0,0 +1,64 @@
+/*
+ * 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.webkit;
+
+/**
+ * Public class representing a JavaScript console message from WebCore. This could be a issued
+ * by a call to one of the <code>console</code> logging functions (e.g.
+ * <code>console.log('...')</code>) or a JavaScript error on the page. To receive notifications
+ * of these messages, override the
+ * {@link WebChromeClient#onConsoleMessage(ConsoleMessage)} function.
+ */
+public class ConsoleMessage {
+
+ // This must be kept in sync with the WebCore enum in WebCore/page/Console.h
+ public enum MessageLevel {
+ TIP,
+ LOG,
+ WARNING,
+ ERROR,
+ DEBUG
+ };
+
+ private MessageLevel mLevel;
+ private String mMessage;
+ private String mSourceId;
+ private int mLineNumber;
+
+ public ConsoleMessage(String message, String sourceId, int lineNumber, MessageLevel msgLevel) {
+ mMessage = message;
+ mSourceId = sourceId;
+ mLineNumber = lineNumber;
+ mLevel = msgLevel;
+ }
+
+ public MessageLevel messageLevel() {
+ return mLevel;
+ }
+
+ public String message() {
+ return mMessage;
+ }
+
+ public String sourceId() {
+ return mSourceId;
+ }
+
+ public int lineNumber() {
+ return mLineNumber;
+ }
+};
diff --git a/core/java/android/webkit/ContentLoader.java b/core/java/android/webkit/ContentLoader.java
index 5eb54b0..d13210aa 100644
--- a/core/java/android/webkit/ContentLoader.java
+++ b/core/java/android/webkit/ContentLoader.java
@@ -16,14 +16,10 @@
package android.webkit;
-import android.content.Context;
import android.net.http.EventHandler;
import android.net.http.Headers;
import android.net.Uri;
-import java.io.File;
-import java.io.FileInputStream;
-
/**
* This class is a concrete implementation of StreamLoader that loads
* "content:" URIs
@@ -68,7 +64,7 @@
protected boolean setupStreamAndSendStatus() {
Uri uri = Uri.parse(mUrl);
if (uri == null) {
- mHandler.error(
+ mLoadListener.error(
EventHandler.FILE_NOT_FOUND_ERROR,
mContext.getString(
com.android.internal.R.string.httpErrorBadUrl) +
@@ -78,18 +74,14 @@
try {
mDataStream = mContext.getContentResolver().openInputStream(uri);
- mHandler.status(1, 1, 200, "OK");
+ mLoadListener.status(1, 1, 200, "OK");
} catch (java.io.FileNotFoundException ex) {
- mHandler.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
- return false;
-
- } catch (java.io.IOException ex) {
- mHandler.error(EventHandler.FILE_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
return false;
} catch (RuntimeException ex) {
// readExceptionWithFileNotFoundExceptionFromParcel in DatabaseUtils
// can throw a serial of RuntimeException. Catch them all here.
- mHandler.error(EventHandler.FILE_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_ERROR, errString(ex));
return false;
}
return true;
@@ -103,16 +95,4 @@
// content can change, we don't want WebKit to cache it
headers.setCacheControl("no-store, no-cache");
}
-
- /**
- * Construct a ContentLoader and instruct it to start loading.
- *
- * @param url "content:" url pointing to content to be loaded
- * @param loadListener LoadListener to pass the content to
- */
- public static void requestUrl(String url, LoadListener loadListener) {
- ContentLoader loader = new ContentLoader(url, loadListener);
- loader.load();
- }
-
}
diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java
index 2a68a5d8..235dc5be 100644
--- a/core/java/android/webkit/DataLoader.java
+++ b/core/java/android/webkit/DataLoader.java
@@ -62,10 +62,10 @@
@Override
protected boolean setupStreamAndSendStatus() {
if (mDataStream != null) {
- mHandler.status(1, 1, 200, "OK");
+ mLoadListener.status(1, 1, 200, "OK");
return true;
} else {
- mHandler.error(EventHandler.ERROR,
+ mLoadListener.error(EventHandler.ERROR,
mContext.getString(R.string.httpError));
return false;
}
@@ -74,16 +74,4 @@
@Override
protected void buildHeaders(android.net.http.Headers h) {
}
-
- /**
- * Construct a DataLoader and instruct it to start loading.
- *
- * @param url data: URL string optionally containing a mimetype
- * @param loadListener LoadListener to pass the content to
- */
- public static void requestUrl(String url, LoadListener loadListener) {
- DataLoader loader = new DataLoader(url, loadListener);
- loader.load();
- }
-
}
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
index e856cde..e21e9ef8 100644
--- a/core/java/android/webkit/FileLoader.java
+++ b/core/java/android/webkit/FileLoader.java
@@ -18,11 +18,9 @@
import com.android.internal.R;
-import android.content.Context;
import android.content.res.AssetManager;
import android.net.http.EventHandler;
import android.net.http.Headers;
-import android.os.Environment;
import android.util.Log;
import android.util.TypedValue;
@@ -111,7 +109,7 @@
// "<package>.R$drawable"
if (mPath == null || mPath.length() == 0) {
Log.e(LOGTAG, "Need a path to resolve the res file");
- mHandler.error(EventHandler.FILE_ERROR, mContext
+ mLoadListener.error(EventHandler.FILE_ERROR, mContext
.getString(R.string.httpErrorFileNotFound));
return false;
@@ -120,7 +118,7 @@
int dot = mPath.indexOf('.', slash);
if (slash == -1 || dot == -1) {
Log.e(LOGTAG, "Incorrect res path: " + mPath);
- mHandler.error(EventHandler.FILE_ERROR, mContext
+ mLoadListener.error(EventHandler.FILE_ERROR, mContext
.getString(R.string.httpErrorFileNotFound));
return false;
}
@@ -157,13 +155,13 @@
errorMsg = "Caught IllegalAccessException: " + e;
}
if (errorMsg != null) {
- mHandler.error(EventHandler.FILE_ERROR, mContext
+ mLoadListener.error(EventHandler.FILE_ERROR, mContext
.getString(R.string.httpErrorFileNotFound));
return false;
}
} else {
if (!mAllowFileAccess) {
- mHandler.error(EventHandler.FILE_ERROR,
+ mLoadListener.error(EventHandler.FILE_ERROR,
mContext.getString(R.string.httpErrorFileNotFound));
return false;
}
@@ -171,14 +169,14 @@
mDataStream = new FileInputStream(mPath);
mContentLength = (new File(mPath)).length();
}
- mHandler.status(1, 1, 200, "OK");
+ mLoadListener.status(1, 1, 200, "OK");
} catch (java.io.FileNotFoundException ex) {
- mHandler.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
return false;
} catch (java.io.IOException ex) {
- mHandler.error(EventHandler.FILE_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_ERROR, errString(ex));
return false;
}
return true;
@@ -188,22 +186,4 @@
protected void buildHeaders(Headers headers) {
// do nothing.
}
-
-
- /**
- * Construct a FileLoader and instruct it to start loading.
- *
- * @param url Full file url pointing to content to be loaded
- * @param loadListener LoadListener to pass the content to
- * @param asset true if url points to an asset.
- * @param allowFileAccess true if this FileLoader can load files from the
- * file system.
- */
- public static void requestUrl(String url, LoadListener loadListener,
- int type, boolean allowFileAccess) {
- FileLoader loader = new FileLoader(url, loadListener, type,
- allowFileAccess);
- loader.load();
- }
-
}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index 58eca38..b13c405 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -141,24 +141,29 @@
return true;
}
if (URLUtil.isAssetUrl(url)) {
- FileLoader.requestUrl(url, loadListener, FileLoader.TYPE_ASSET,
- true);
+ // load asset in a separate thread as it involves IO
+ new FileLoader(url, loadListener, FileLoader.TYPE_ASSET, true)
+ .enqueue();
return true;
} else if (URLUtil.isResourceUrl(url)) {
- FileLoader.requestUrl(url, loadListener, FileLoader.TYPE_RES,
- true);
+ // load resource in a separate thread as it involves IO
+ new FileLoader(url, loadListener, FileLoader.TYPE_RES, true)
+ .enqueue();
return true;
} else if (URLUtil.isFileUrl(url)) {
- FileLoader.requestUrl(url, loadListener, FileLoader.TYPE_FILE,
- settings.getAllowFileAccess());
+ // load file in a separate thread as it involves IO
+ new FileLoader(url, loadListener, FileLoader.TYPE_FILE, settings
+ .getAllowFileAccess()).enqueue();
return true;
} else if (URLUtil.isContentUrl(url)) {
// Send the raw url to the ContentLoader because it will do a
- // permission check and the url has to match..
- ContentLoader.requestUrl(loadListener.url(), loadListener);
+ // permission check and the url has to match.
+ // load content in a separate thread as it involves IO
+ new ContentLoader(loadListener.url(), loadListener).enqueue();
return true;
} else if (URLUtil.isDataUrl(url)) {
- DataLoader.requestUrl(url, loadListener);
+ // load data in the current thread to reduce the latency
+ new DataLoader(url, loadListener).load();
return true;
} else if (URLUtil.isAboutUrl(url)) {
loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
diff --git a/core/java/android/webkit/StreamLoader.java b/core/java/android/webkit/StreamLoader.java
index ce26268..4c32997 100644
--- a/core/java/android/webkit/StreamLoader.java
+++ b/core/java/android/webkit/StreamLoader.java
@@ -20,12 +20,13 @@
import android.net.http.EventHandler;
import android.net.http.Headers;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.os.Message;
import java.io.IOException;
import java.io.InputStream;
-
/**
* This abstract class is used for all content loaders that rely on streaming
* content into the rendering engine loading framework.
@@ -44,9 +45,7 @@
* that indicates the content should not be cached.
*
*/
-abstract class StreamLoader extends Handler {
-
- public static final String NO_STORE = "no-store";
+abstract class StreamLoader implements Handler.Callback {
private static final int MSG_STATUS = 100; // Send status to loader
private static final int MSG_HEADERS = 101; // Send headers to loader
@@ -54,11 +53,19 @@
private static final int MSG_END = 103; // Send endData to loader
protected final Context mContext;
- protected final LoadListener mHandler; // loader class
+ protected final LoadListener mLoadListener; // loader class
protected InputStream mDataStream; // stream to read data from
protected long mContentLength; // content length of data
private byte [] mData; // buffer to pass data to loader with.
+ // Handler which will be initialized in the thread where load() is called.
+ private Handler mHandler;
+
+ // Handler which will be used to load StreamLoader in a separate thread
+ private static StreamQueueHandler sStreamQueueHandler;
+
+ private static final Object sStreamQueueLock = new Object();
+
/**
* Constructor. Although this class calls the LoadListener, it only calls
* the EventHandler Interface methods. LoadListener concrete class is used
@@ -67,13 +74,13 @@
* @param loadlistener The LoadListener to call with the data.
*/
StreamLoader(LoadListener loadlistener) {
- mHandler = loadlistener;
+ mLoadListener = loadlistener;
mContext = loadlistener.getContext();
}
/**
* This method is called when the derived class should setup mDataStream,
- * and call mHandler.status() to indicate that the load can occur. If it
+ * and call mLoadListener.status() to indicate that the load can occur. If it
* fails to setup, it should still call status() with the error code.
*
* @return true if stream was successfully setup
@@ -89,15 +96,40 @@
*/
abstract protected void buildHeaders(Headers headers);
+ /**
+ * Calling this method to load this StreamLoader in a separate
+ * "StreamLoadingThread".
+ */
+ final void enqueue() {
+ synchronized (sStreamQueueLock) {
+ if (sStreamQueueHandler == null) {
+ HandlerThread thread = new HandlerThread(
+ StreamQueueHandler.THREAD_NAME,
+ android.os.Process.THREAD_PRIORITY_DEFAULT +
+ android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
+ thread.start();
+ sStreamQueueHandler = new StreamQueueHandler(thread.getLooper());
+ }
+ }
+
+ sStreamQueueHandler.obtainMessage(StreamQueueHandler.MSG_ADD_LOADER,
+ this).sendToTarget();
+ }
/**
* Calling this method starts the load of the content for this StreamLoader.
- * This method simply posts a message to send the status and returns
- * immediately.
+ * This method simply creates a Handler in the current thread and posts a
+ * message to send the status and returns immediately.
*/
- public void load() {
- if (!mHandler.isSynchronous()) {
- sendMessage(obtainMessage(MSG_STATUS));
+ final void load() {
+ synchronized (this) {
+ if (mHandler == null) {
+ mHandler = new Handler(this);
+ }
+ }
+
+ if (!mLoadListener.isSynchronous()) {
+ mHandler.sendEmptyMessage(MSG_STATUS);
} else {
// Load the stream synchronously.
if (setupStreamAndSendStatus()) {
@@ -105,23 +137,20 @@
// to pass data to the loader
mData = new byte[8192];
sendHeaders();
- while (!sendData() && !mHandler.cancelled());
+ while (!sendData() && !mLoadListener.cancelled());
closeStreamAndSendEndData();
- mHandler.loadSynchronousMessages();
+ mLoadListener.loadSynchronousMessages();
}
}
}
- /* (non-Javadoc)
- * @see android.os.Handler#handleMessage(android.os.Message)
- */
- public void handleMessage(Message msg) {
- if (DebugFlags.STREAM_LOADER && mHandler.isSynchronous()) {
+ public boolean handleMessage(Message msg) {
+ if (mLoadListener.isSynchronous()) {
throw new AssertionError();
}
- if (mHandler.cancelled()) {
+ if (mLoadListener.cancelled()) {
closeStreamAndSendEndData();
- return;
+ return true;
}
switch(msg.what) {
case MSG_STATUS:
@@ -129,27 +158,27 @@
// We were able to open the stream, create the array
// to pass data to the loader
mData = new byte[8192];
- sendMessage(obtainMessage(MSG_HEADERS));
+ mHandler.sendEmptyMessage(MSG_HEADERS);
}
break;
case MSG_HEADERS:
sendHeaders();
- sendMessage(obtainMessage(MSG_DATA));
+ mHandler.sendEmptyMessage(MSG_DATA);
break;
case MSG_DATA:
if (sendData()) {
- sendMessage(obtainMessage(MSG_END));
+ mHandler.sendEmptyMessage(MSG_END);
} else {
- sendMessage(obtainMessage(MSG_DATA));
+ mHandler.sendEmptyMessage(MSG_DATA);
}
break;
case MSG_END:
closeStreamAndSendEndData();
break;
default:
- super.handleMessage(msg);
- break;
+ return false;
}
+ return true;
}
/**
@@ -161,7 +190,7 @@
headers.setContentLength(mContentLength);
}
buildHeaders(headers);
- mHandler.headers(headers);
+ mLoadListener.headers(headers);
}
/**
@@ -176,12 +205,11 @@
try {
int amount = mDataStream.read(mData);
if (amount > 0) {
- mHandler.data(mData, amount);
+ mLoadListener.data(mData, amount);
return false;
}
} catch (IOException ex) {
- mHandler.error(EventHandler.FILE_ERROR,
- ex.getMessage());
+ mLoadListener.error(EventHandler.FILE_ERROR, ex.getMessage());
}
}
return true;
@@ -198,7 +226,24 @@
// ignore.
}
}
- mHandler.endData();
+ mLoadListener.endData();
}
+ private static class StreamQueueHandler extends Handler {
+ private static final String THREAD_NAME = "StreamLoadingThread";
+
+ private static final int MSG_ADD_LOADER = 101;
+
+ StreamQueueHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_ADD_LOADER) {
+ StreamLoader loader = (StreamLoader) msg.obj;
+ loader.load();
+ }
+ }
+ }
}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index f40b55c..1d5aac7 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -262,8 +262,24 @@
* @param message The error message to report.
* @param lineNumber The line number of the error.
* @param sourceID The name of the source file that caused the error.
+ * @deprecated Use {@link #onConsoleMessage(ConsoleMessage) onConsoleMessage(ConsoleMessage)}
+ * instead.
*/
- public void onConsoleMessage(String message, int lineNumber, String sourceID) {}
+ @Deprecated
+ public void onConsoleMessage(String message, int lineNumber, String sourceID) { }
+
+ /**
+ * Report a JavaScript console message to the host application. The ChromeClient
+ * should override this to process the log message as they see fit.
+ * @param consoleMessage Object containing details of the console message.
+ * @return true if the message is handled by the client.
+ */
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+ // Call the old version of this function for backwards compatability.
+ onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(),
+ consoleMessage.sourceId());
+ return false;
+ }
/**
* When not playing, video elements are represented by a 'poster' image. The
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 6d0be43..68be08d 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -145,6 +145,12 @@
break;
}
+ if (KeyEvent.KEYCODE_TAB == keyCode) {
+ if (down) {
+ onEditorAction(EditorInfo.IME_ACTION_NEXT);
+ }
+ return true;
+ }
Spannable text = (Spannable) getText();
int oldLength = text.length();
// Normally the delete key's dom events are sent via onTextChanged.
@@ -819,6 +825,7 @@
break;
case 2: // PASSWORD
inPassword = true;
+ imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case 3: // SEARCH
imeOptions |= EditorInfo.IME_ACTION_SEARCH;
@@ -826,20 +833,22 @@
case 4: // EMAIL
// TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents EMAIL_ADDRESS
// from working, so exclude it for now.
- inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+ imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case 5: // NUMBER
- inputType = EditorInfo.TYPE_CLASS_NUMBER;
+ inputType |= EditorInfo.TYPE_CLASS_NUMBER;
+ // Number and telephone do not have both a Tab key and an
+ // action, so set the action to NEXT
+ imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case 6: // TELEPHONE
- inputType = EditorInfo.TYPE_CLASS_PHONE;
+ inputType |= EditorInfo.TYPE_CLASS_PHONE;
+ imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case 7: // URL
- // TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents URI
- // from working, so exclude it for now.
- inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_URI;
+ // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
+ // exclude it for now.
+ imeOptions |= EditorInfo.IME_ACTION_GO;
break;
default:
break;
@@ -855,22 +864,6 @@
mWebView.requestFormData(name, mNodePointer);
}
}
- if (type != 3 /* SEARCH */) {
- int action = mWebView.nativeTextFieldAction();
- switch (action) {
- // Keep in sync with CachedRoot::ImeAction
- case 0: // NEXT
- imeOptions |= EditorInfo.IME_ACTION_NEXT;
- break;
- case 1: // GO
- imeOptions |= EditorInfo.IME_ACTION_GO;
- break;
- case -1: // FAILURE
- case 2: // DONE
- imeOptions |= EditorInfo.IME_ACTION_DONE;
- break;
- }
- }
}
mSingle = single;
setMaxLength(maxLength);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6627973..52c0e01 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -26,6 +26,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Interpolator;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
@@ -2972,8 +2973,16 @@
if (mTitleBar != null) {
canvas.translate(0, (int) mTitleBar.getHeight());
}
- if (mDragTrackerHandler == null || !mDragTrackerHandler.draw(canvas)) {
+ if (mDragTrackerHandler == null) {
drawContent(canvas);
+ } else {
+ if (!mDragTrackerHandler.draw(canvas)) {
+ // sometimes the tracker doesn't draw, even though its active
+ drawContent(canvas);
+ }
+ if (mDragTrackerHandler.isFinished()) {
+ mDragTrackerHandler = null;
+ }
}
canvas.restoreToCount(saveCount);
@@ -4066,6 +4075,14 @@
private final float mMaxDY, mMaxDX;
private float mCurrStretchY, mCurrStretchX;
private int mSX, mSY;
+ private Interpolator mInterp;
+ private float[] mXY = new float[2];
+
+ // inner (non-state) classes can't have enums :(
+ private static final int DRAGGING_STATE = 0;
+ private static final int ANIMATING_STATE = 1;
+ private static final int FINISHED_STATE = 2;
+ private int mState;
public DragTrackerHandler(float x, float y, DragTracker proxy) {
mProxy = proxy;
@@ -4090,6 +4107,7 @@
mMinDX = -viewLeft;
mMaxDX = docRight - viewRight;
+ mState = DRAGGING_STATE;
mProxy.onStartDrag(x, y);
// ensure we buildBitmap at least once
@@ -4112,6 +4130,12 @@
float sy = computeStretch(mStartY - y, mMinDY, mMaxDY);
float sx = computeStretch(mStartX - x, mMinDX, mMaxDX);
+ if ((mSnapScrollMode & SNAP_X) != 0) {
+ sy = 0;
+ } else if ((mSnapScrollMode & SNAP_Y) != 0) {
+ sx = 0;
+ }
+
if (mCurrStretchX != sx || mCurrStretchY != sy) {
mCurrStretchX = sx;
mCurrStretchY = sy;
@@ -4126,10 +4150,26 @@
}
public void stopDrag() {
+ final int DURATION = 200;
+ int now = (int)SystemClock.uptimeMillis();
+ mInterp = new Interpolator(2);
+ mXY[0] = mCurrStretchX;
+ mXY[1] = mCurrStretchY;
+ // float[] blend = new float[] { 0.5f, 0, 0.75f, 1 };
+ float[] blend = new float[] { 0, 0.5f, 0.75f, 1 };
+ mInterp.setKeyFrame(0, now, mXY, blend);
+ float[] zerozero = new float[] { 0, 0 };
+ mInterp.setKeyFrame(1, now + DURATION, zerozero, null);
+ mState = ANIMATING_STATE;
+
if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
- Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag");
+ Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag, starting animation");
}
- mProxy.onStopDrag();
+ }
+
+ // Call this after each draw. If it ruturns null, the tracker is done
+ public boolean isFinished() {
+ return mState == FINISHED_STATE;
}
private int hiddenHeightOfTitleBar() {
@@ -4150,13 +4190,23 @@
if (mCurrStretchX != 0 || mCurrStretchY != 0) {
int sx = getScrollX();
int sy = getScrollY() - hiddenHeightOfTitleBar();
-
if (mSX != sx || mSY != sy) {
buildBitmap(sx, sy);
mSX = sx;
mSY = sy;
}
+ if (mState == ANIMATING_STATE) {
+ Interpolator.Result result = mInterp.timeToValues(mXY);
+ if (result == Interpolator.Result.FREEZE_END) {
+ mState = FINISHED_STATE;
+ return false;
+ } else {
+ mProxy.onStretchChange(mXY[0], mXY[1]);
+ invalidate();
+ // fall through to the draw
+ }
+ }
int count = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(sx, sy);
mProxy.onDraw(canvas);
@@ -4600,7 +4650,6 @@
case MotionEvent.ACTION_UP: {
if (mDragTrackerHandler != null) {
mDragTrackerHandler.stopDrag();
- mDragTrackerHandler = null;
}
mLastTouchUpTime = eventTime;
switch (mTouchMode) {
@@ -4712,7 +4761,6 @@
private void cancelTouch() {
if (mDragTrackerHandler != null) {
mDragTrackerHandler.stopDrag();
- mDragTrackerHandler = null;
}
// we also use mVelocityTracker == null to tell us that we are
// not "moving around", so we can take the slower/prettier
@@ -6736,8 +6784,6 @@
private native void nativeSetFindIsUp();
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
- // Returns a value corresponding to CachedFrame::ImeAction
- /* package */ native int nativeTextFieldAction();
private native int nativeTextGeneration();
// Never call this version except by updateCachedTextfield(String) -
// we always want to pass in our generation number.
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 6e45e39..9c91919 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -260,9 +260,12 @@
* @param message The message to add
* @param lineNumber the line on which the error occurred
* @param sourceID the filename of the source that caused the error.
+ * @param msgLevel the log level of this message. This is a value casted to int
+ * from WebCore::MessageLevel in WebCore/page/Console.h.
*/
- protected void addMessageToConsole(String message, int lineNumber, String sourceID) {
- mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID);
+ protected void addMessageToConsole(String message, int lineNumber, String sourceID,
+ int msgLevel) {
+ mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID, msgLevel);
}
/**
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1cc1c26..2f96aef 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -32,7 +32,6 @@
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -307,6 +306,11 @@
* Handles one frame of a fling
*/
private FlingRunnable mFlingRunnable;
+
+ /**
+ * Handles scrolling between positions within the list.
+ */
+ private PositionScroller mPositionScroller;
/**
* The offset in pixels form the top of the AdapterView to the top
@@ -1588,6 +1592,10 @@
// let the fling runnable report it's new state which
// should be idle
mFlingRunnable.endFling();
+ if (mScrollY != 0) {
+ mScrollY = 0;
+ invalidate();
+ }
}
// Always hide the type filter
dismissPopup();
@@ -1892,9 +1900,10 @@
// Check if we have moved far enough that it looks more like a
// scroll than a tap
final int distance = Math.abs(deltaY);
- if (distance > mTouchSlop) {
+ final boolean overscroll = mScrollY != 0;
+ if (overscroll || distance > mTouchSlop) {
createScrollingCache();
- mTouchMode = TOUCH_MODE_SCROLL;
+ mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
mMotionCorrection = deltaY;
final Handler handler = getHandler();
// Handler should not be null unless the AbsListView is not attached to a
@@ -1934,12 +1943,17 @@
} else {
int touchMode = mTouchMode;
if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
- mScrollY = 0;
if (mFlingRunnable != null) {
mFlingRunnable.endFling();
+
+ if (mScrollY != 0) {
+ mScrollY = 0;
+ invalidate();
+ }
}
}
}
+ mLastTouchMode = isInTouchMode ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
}
@Override
@@ -2051,9 +2065,11 @@
if (motionView != null) {
motionViewPrevTop = motionView.getTop();
}
+
// No need to do all this work if we're not going to move anyway
+ boolean atEdge = false;
if (incrementalDeltaY != 0) {
- trackMotionScroll(deltaY, incrementalDeltaY);
+ atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
}
// Check to see if we have bumped into the scroll limit
@@ -2063,7 +2079,7 @@
// supposed to be
final int motionViewRealTop = motionView.getTop();
final int motionViewNewTop = mMotionViewNewTop;
- if (motionViewRealTop != motionViewNewTop) {
+ if (atEdge) {
// Apply overscroll
mScrollY -= incrementalDeltaY - (motionViewRealTop - motionViewPrevTop);
@@ -2097,7 +2113,6 @@
// Check to see if we are back in
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
if (motionView != null) {
- int topOffset = motionView.getTop() - mMotionViewNewTop;
mTouchMode = TOUCH_MODE_SCROLL;
// We did not scroll the full amount. Treat this essentially like the
@@ -2440,7 +2455,7 @@
}
}
}
-
+
void startSpringback() {
if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
mTouchMode = TOUCH_MODE_OVERFLING;
@@ -2448,19 +2463,33 @@
post(this);
}
}
-
+
void startOverfling(int initialVelocity) {
mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 0, 0, 0, getHeight());
mTouchMode = TOUCH_MODE_OVERFLING;
invalidate();
post(this);
}
-
+
+ void startScroll(int distance, int duration) {
+ int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
+ mLastFlingY = initialY;
+ mScroller.startScroll(0, initialY, 0, distance, duration);
+ mTouchMode = TOUCH_MODE_FLING;
+ post(this);
+ }
+
private void endFling() {
mTouchMode = TOUCH_MODE_REST;
+
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
clearScrollingCache();
+
removeCallbacks(this);
+
+ if (mPositionScroller != null) {
+ removeCallbacks(mPositionScroller);
+ }
}
public void run() {
@@ -2553,6 +2582,278 @@
}
}
+
+
+ class PositionScroller implements Runnable {
+ private static final int SCROLL_DURATION = 400;
+
+ private static final int MOVE_DOWN_POS = 1;
+ private static final int MOVE_UP_POS = 2;
+ private static final int MOVE_DOWN_BOUND = 3;
+ private static final int MOVE_UP_BOUND = 4;
+
+ private int mMode;
+ private int mTargetPos;
+ private int mBoundPos;
+ private int mLastSeenPos;
+ private int mScrollDuration;
+ private int mExtraScroll;
+
+ PositionScroller() {
+ mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
+ }
+
+ void start(int position) {
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + getChildCount() - 1;
+
+ int viewTravelCount = 0;
+ if (position <= firstPos) {
+ viewTravelCount = firstPos - position + 1;
+ mMode = MOVE_UP_POS;
+ } else if (position >= lastPos) {
+ viewTravelCount = position - lastPos + 1;
+ mMode = MOVE_DOWN_POS;
+ } else {
+ // Already on screen, nothing to do
+ return;
+ }
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = position;
+ mBoundPos = INVALID_POSITION;
+ mLastSeenPos = INVALID_POSITION;
+
+ post(this);
+ }
+
+ void start(int position, int boundPosition) {
+ if (boundPosition == INVALID_POSITION) {
+ start(position);
+ return;
+ }
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + getChildCount() - 1;
+
+ int viewTravelCount = 0;
+ if (position < firstPos) {
+ final int boundPosFromLast = lastPos - boundPosition;
+ if (boundPosFromLast < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = firstPos - position + 1;
+ final int boundTravel = boundPosFromLast - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_UP_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_UP_POS;
+ }
+ } else if (position > lastPos) {
+ final int boundPosFromFirst = boundPosition - firstPos;
+ if (boundPosFromFirst < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = position - lastPos + 1;
+ final int boundTravel = boundPosFromFirst - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_DOWN_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_DOWN_POS;
+ }
+ } else {
+ // Already on screen, nothing to do
+ return;
+ }
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = position;
+ mBoundPos = boundPosition;
+ mLastSeenPos = INVALID_POSITION;
+
+ post(this);
+ }
+
+ void stop() {
+ removeCallbacks(this);
+ }
+
+ public void run() {
+ final int listHeight = getHeight();
+ final int firstPos = mFirstPosition;
+
+ switch (mMode) {
+ case MOVE_DOWN_POS: {
+ final int lastViewIndex = getChildCount() - 1;
+ final int lastPos = firstPos + lastViewIndex;
+
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
+
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+
+ smoothScrollBy(lastViewHeight - lastViewPixelsShowing + mExtraScroll,
+ mScrollDuration);
+
+ mLastSeenPos = lastPos;
+ if (lastPos != mTargetPos) {
+ post(this);
+ }
+ break;
+ }
+
+ case MOVE_DOWN_BOUND: {
+ final int nextViewIndex = 1;
+ if (firstPos == mBoundPos || getChildCount() <= nextViewIndex) {
+ return;
+ }
+ final int nextPos = firstPos + nextViewIndex;
+
+ if (nextPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
+
+ final View nextView = getChildAt(nextViewIndex);
+ final int nextViewHeight = nextView.getHeight();
+ final int nextViewTop = nextView.getTop();
+ final int extraScroll = mExtraScroll;
+ if (nextPos != mBoundPos) {
+ smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
+ mScrollDuration);
+
+ mLastSeenPos = nextPos;
+
+ post(this);
+ } else {
+ if (nextViewTop > extraScroll) {
+ smoothScrollBy(nextViewTop - extraScroll, mScrollDuration);
+ }
+ }
+ break;
+ }
+
+ case MOVE_UP_POS: {
+ if (firstPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
+
+ final View firstView = getChildAt(0);
+ final int firstViewTop = firstView.getTop();
+
+ smoothScrollBy(firstViewTop - mExtraScroll, mScrollDuration);
+
+ mLastSeenPos = firstPos;
+
+ if (firstPos != mTargetPos) {
+ post(this);
+ }
+ break;
+ }
+
+ case MOVE_UP_BOUND: {
+ final int lastViewIndex = getChildCount() - 2;
+ if (lastViewIndex < 0) {
+ return;
+ }
+ final int lastPos = firstPos + lastViewIndex;
+
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
+
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+ mLastSeenPos = lastPos;
+ if (lastPos != mBoundPos) {
+ smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration);
+ post(this);
+ } else {
+ final int bottom = listHeight - mExtraScroll;
+ final int lastViewBottom = lastViewTop + lastViewHeight;
+ if (bottom > lastViewBottom) {
+ smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration);
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Smoothly scroll to the specified adapter position. The view will
+ * scroll such that the indicated position is displayed.
+ * @param position Scroll to this adapter position.
+ */
+ public void smoothScrollToPosition(int position) {
+ if (mPositionScroller == null) {
+ mPositionScroller = new PositionScroller();
+ }
+ mPositionScroller.start(position);
+ }
+
+ /**
+ * Smoothly scroll to the specified adapter position. The view will
+ * scroll such that the indicated position is displayed, but it will
+ * stop early if scrolling further would scroll boundPosition out of
+ * view.
+ * @param position Scroll to this adapter position.
+ * @param boundPosition Do not scroll if it would move this adapter
+ * position out of view.
+ */
+ public void smoothScrollToPosition(int position, int boundPosition) {
+ if (mPositionScroller == null) {
+ mPositionScroller = new PositionScroller();
+ }
+ mPositionScroller.start(position, boundPosition);
+ }
+
+ /**
+ * Smoothly scroll by distance pixels over duration milliseconds.
+ * @param distance Distance to scroll in pixels.
+ * @param duration Duration of the scroll animation in milliseconds.
+ */
+ public void smoothScrollBy(int distance, int duration) {
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ } else {
+ mFlingRunnable.endFling();
+ }
+ mFlingRunnable.startScroll(distance, duration);
+ }
private void createScrollingCache() {
if (mScrollingCacheEnabled && !mCachingStarted) {
@@ -2588,11 +2889,12 @@
* @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
* began. Positive numbers mean the user's finger is moving down the screen.
* @param incrementalDeltaY Change in deltaY from the previous event.
+ * @return true if we're already at the beginning/end of the list and have nothing to do.
*/
- void trackMotionScroll(int deltaY, int incrementalDeltaY) {
+ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
- return;
+ return true;
}
final int firstTop = getChildAt(0).getTop();
@@ -2618,98 +2920,99 @@
incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
}
- final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+ final int firstPosition = mFirstPosition;
- if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) {
- hideSelector();
- offsetChildrenTopAndBottom(incrementalDeltaY);
- if (!awakenScrollBars()) {
- invalidate();
- }
- mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
- } else {
- final int firstPosition = mFirstPosition;
-
- if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
- // Don't need to move views down if the top of the first position is already visible
- return;
- }
-
- if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
- // Don't need to move views up if the bottom of the last position is already visible
- return;
- }
-
- final boolean down = incrementalDeltaY < 0;
-
- hideSelector();
-
- final int headerViewsCount = getHeaderViewsCount();
- final int footerViewsStart = mItemCount - getFooterViewsCount();
-
- int start = 0;
- int count = 0;
-
- if (down) {
- final int top = listPadding.top - incrementalDeltaY;
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getBottom() >= top) {
- break;
- } else {
- count++;
- int position = firstPosition + i;
- if (position >= headerViewsCount && position < footerViewsStart) {
- mRecycler.addScrapView(child);
-
- if (ViewDebug.TRACE_RECYCLER) {
- ViewDebug.trace(child,
- ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
- firstPosition + i, -1);
- }
- }
- }
- }
- } else {
- final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
- for (int i = childCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
- if (child.getTop() <= bottom) {
- break;
- } else {
- start = i;
- count++;
- int position = firstPosition + i;
- if (position >= headerViewsCount && position < footerViewsStart) {
- mRecycler.addScrapView(child);
-
- if (ViewDebug.TRACE_RECYCLER) {
- ViewDebug.trace(child,
- ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
- firstPosition + i, -1);
- }
- }
- }
- }
- }
-
- mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
-
- mBlockLayoutRequests = true;
- detachViewsFromParent(start, count);
- offsetChildrenTopAndBottom(incrementalDeltaY);
-
- if (down) {
- mFirstPosition += count;
- }
-
- invalidate();
- fillGap(down);
- mBlockLayoutRequests = false;
-
- invokeOnItemScrollListener();
- awakenScrollBars();
+ if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
+ // Don't need to move views down if the top of the first position
+ // is already visible
+ return true;
}
+
+ if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
+ // Don't need to move views up if the bottom of the last position
+ // is already visible
+ return true;
+ }
+
+ final boolean down = incrementalDeltaY < 0;
+
+ hideSelector();
+
+ final int headerViewsCount = getHeaderViewsCount();
+ final int footerViewsStart = mItemCount - getFooterViewsCount();
+
+ int start = 0;
+ int count = 0;
+
+ if (down) {
+ final int top = listPadding.top - incrementalDeltaY;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getBottom() >= top) {
+ break;
+ } else {
+ count++;
+ int position = firstPosition + i;
+ if (position >= headerViewsCount && position < footerViewsStart) {
+ mRecycler.addScrapView(child);
+
+ if (ViewDebug.TRACE_RECYCLER) {
+ ViewDebug.trace(child,
+ ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+ firstPosition + i, -1);
+ }
+ }
+ }
+ }
+ } else {
+ final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ if (child.getTop() <= bottom) {
+ break;
+ } else {
+ start = i;
+ count++;
+ int position = firstPosition + i;
+ if (position >= headerViewsCount && position < footerViewsStart) {
+ mRecycler.addScrapView(child);
+
+ if (ViewDebug.TRACE_RECYCLER) {
+ ViewDebug.trace(child,
+ ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+ firstPosition + i, -1);
+ }
+ }
+ }
+ }
+ }
+
+ mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
+
+ mBlockLayoutRequests = true;
+
+ if (count > 0) {
+ detachViewsFromParent(start, count);
+ }
+ offsetChildrenTopAndBottom(incrementalDeltaY);
+
+ if (down) {
+ mFirstPosition += count;
+ }
+
+ invalidate();
+
+ final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+ if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
+ fillGap(down);
+ }
+
+ mBlockLayoutRequests = false;
+
+ invokeOnItemScrollListener();
+ awakenScrollBars();
+
+ return false;
}
/**
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 405461a..a4b20da 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -18,14 +18,12 @@
import com.android.internal.R;
-import java.util.ArrayList;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -35,6 +33,8 @@
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.ExpandableListConnector.PositionMetadata;
+import java.util.ArrayList;
+
/**
* A view that shows items in a vertically scrolling two-level list. This
* differs from the {@link ListView} by allowing two levels: groups which can
@@ -541,6 +541,12 @@
if (mOnGroupExpandListener != null) {
mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
}
+
+ final int groupPos = posMetadata.position.groupPos;
+ final int groupFlatPos = posMetadata.position.flatListPos;
+
+ smoothScrollToPosition(groupFlatPos + mAdapter.getChildrenCount(groupPos),
+ groupFlatPos);
}
returnValue = true;
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index b4e2790..ea5841a 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -360,7 +360,8 @@
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
- mTotalLength += lp.topMargin + lp.bottomMargin;
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
} else {
int oldHeight = Integer.MIN_VALUE;
@@ -385,8 +386,9 @@
}
final int childHeight = child.getMeasuredHeight();
- mTotalLength += childHeight + lp.topMargin +
- lp.bottomMargin + getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
+ lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
@@ -459,8 +461,10 @@
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
- mTotalLength += largestChildHeight + lp.topMargin+ lp.bottomMargin +
- getNextLocationOffset(child);
+ // Account for negative margins
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
+ lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
@@ -536,12 +540,14 @@
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
- mTotalLength += child.getMeasuredHeight() + lp.topMargin +
- lp.bottomMargin + getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
+ lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
- mTotalLength += mPaddingTop + mPaddingBottom;
+ mTotalLength += mPaddingTop + mPaddingBottom;
+ // TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
@@ -651,7 +657,8 @@
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
- mTotalLength += lp.leftMargin + lp.rightMargin;
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin);
// Baseline alignment requires to measure widgets to obtain the
// baseline offset (in particular for TextViews).
@@ -686,8 +693,9 @@
}
final int childWidth = child.getMeasuredWidth();
- mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
- getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
+ lp.rightMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildWidth = Math.max(childWidth, largestChildWidth);
@@ -772,8 +780,9 @@
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
- mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
- getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
+ lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
}
}
@@ -843,8 +852,9 @@
}
}
- mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
- lp.rightMargin + getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
+ lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
lp.height == LayoutParams.MATCH_PARENT;
@@ -875,6 +885,7 @@
// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
+ // TODO: Should we update widthSize with the new total length?
// Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
// the most common case
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 2d36bc8..fd18db4 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -39,6 +39,7 @@
* A view for selecting a number
*
* For a dialog using this view, see {@link android.app.TimePickerDialog}.
+ * @hide
*/
@Widget
public class NumberPicker extends LinearLayout {
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 44d415e..b11caa1 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -125,8 +125,8 @@
final float durationSecs = (OVERFLING_DURATION / 1000.f);
int dx = (int)(xvel * durationSecs) / 8;
int dy = (int)(yvel * durationSecs) / 8;
- scroller.startScroll(startx, starty, dx, dy, OVERFLING_DURATION);
mCurrScroller.abortAnimation();
+ scroller.startScroll(startx, starty, dx, dy, OVERFLING_DURATION);
mCurrScroller = scroller;
mScrollMode = MODE_OVERFLING;
}
@@ -164,8 +164,8 @@
}
if (xoff != 0 || yoff != 0) {
- scroller.startScroll(startX, startY, xoff, yoff, SPRINGBACK_DURATION);
mCurrScroller.abortAnimation();
+ scroller.startScroll(startX, startY, xoff, yoff, SPRINGBACK_DURATION);
mCurrScroller = scroller;
mScrollMode = MODE_SPRINGBACK;
return true;
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
index 2b07ae6..7e9bbd1 100644
--- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -24,7 +24,7 @@
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IMountService;
+import android.os.storage.IMountService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
index 98fb236..24818a8 100755
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -24,7 +24,6 @@
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IMountService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index c110f95..2f48499 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -32,7 +32,7 @@
import android.os.Power;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.IMountService;
+import android.os.storage.IMountService;
import com.android.internal.telephony.ITelephony;
import android.util.Log;
diff --git a/core/java/com/android/internal/app/StorageNotification.java b/core/java/com/android/internal/app/StorageNotification.java
new file mode 100644
index 0000000..8876612
--- /dev/null
+++ b/core/java/com/android/internal/app/StorageNotification.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.storage.IMountService;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageResultCode;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class StorageNotification extends StorageEventListener {
+ private static final String TAG = "StorageNotification";
+
+ /**
+ * Binder context for this service
+ */
+ private Context mContext;
+
+ /**
+ * The notification that is shown when a USB mass storage host
+ * is connected.
+ * <p>
+ * This is lazily created, so use {@link #setUsbStorageNotification()}.
+ */
+ private Notification mUsbStorageNotification;
+
+ /**
+ * The notification that is shown when the following media events occur:
+ * - Media is being checked
+ * - Media is blank (or unknown filesystem)
+ * - Media is corrupt
+ * - Media is safe to unmount
+ * - Media is missing
+ * <p>
+ * This is lazily created, so use {@link #setMediaStorageNotification()}.
+ */
+ private Notification mMediaStorageNotification;
+ private boolean mUmsAvailable;
+ private StorageManager mStorageManager;
+
+ public StorageNotification(Context context) {
+ mContext = context;
+
+ mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ mUmsAvailable = mStorageManager.isUsbMassStorageConnected();
+ Log.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", mUmsAvailable,
+ Environment.getExternalStorageState()));
+ }
+
+ /*
+ * @override com.android.os.storage.StorageEventListener
+ */
+ @Override
+ public void onUsbMassStorageConnectionChanged(boolean connected) {
+ mUmsAvailable = connected;
+ /*
+ * Even though we may have a UMS host connected, we the SD card
+ * may not be in a state for export.
+ */
+ String st = Environment.getExternalStorageState();
+
+ Log.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st));
+
+ if (connected && (st.equals(
+ Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {
+ /*
+ * No card or card being checked = don't display
+ */
+ connected = false;
+ }
+ updateUsbMassStorageNotification(connected);
+ }
+
+ /*
+ * @override com.android.os.storage.StorageEventListener
+ */
+ @Override
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ Log.i(TAG, String.format(
+ "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState));
+ if (newState.equals(Environment.MEDIA_SHARED)) {
+ /*
+ * Storage is now shared. Modify the UMS notification
+ * for stopping UMS.
+ */
+ Intent intent = new Intent();
+ intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+ setUsbStorageNotification(
+ com.android.internal.R.string.usb_storage_stop_notification_title,
+ com.android.internal.R.string.usb_storage_stop_notification_message,
+ com.android.internal.R.drawable.stat_sys_warning, false, true, pi);
+ } else if (newState.equals(Environment.MEDIA_CHECKING)) {
+ /*
+ * Storage is now checking. Update media notification and disable
+ * UMS notification.
+ */
+ setMediaStorageNotification(
+ com.android.internal.R.string.ext_media_checking_notification_title,
+ com.android.internal.R.string.ext_media_checking_notification_message,
+ com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null);
+ updateUsbMassStorageNotification(false);
+ } else if (newState.equals(Environment.MEDIA_MOUNTED)) {
+ /*
+ * Storage is now mounted. Dismiss any media notifications,
+ * and enable UMS notification if connected.
+ */
+ setMediaStorageNotification(0, 0, 0, false, false, null);
+ updateUsbMassStorageNotification(mUmsAvailable);
+ } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) {
+ /*
+ * Storage is now unmounted. We may have been unmounted
+ * because the user is enabling/disabling UMS, in which case we don't
+ * want to display the 'safe to unmount' notification.
+ */
+ if (!mStorageManager.isUsbMassStorageEnabled()) {
+ if (oldState.equals(Environment.MEDIA_SHARED)) {
+ /*
+ * The unmount was due to UMS being enabled. Dismiss any
+ * media notifications, and enable UMS notification if connected
+ */
+ setMediaStorageNotification(0, 0, 0, false, false, null);
+ updateUsbMassStorageNotification(mUmsAvailable);
+ } else {
+ /*
+ * Show safe to unmount media notification, and enable UMS
+ * notification if connected.
+ */
+ setMediaStorageNotification(
+ com.android.internal.R.string.ext_media_safe_unmount_notification_title,
+ com.android.internal.R.string.ext_media_safe_unmount_notification_message,
+ com.android.internal.R.drawable.stat_notify_sdcard, true, true, null);
+ updateUsbMassStorageNotification(mUmsAvailable);
+ }
+ } else {
+ /*
+ * The unmount was due to UMS being enabled. Dismiss any
+ * media notifications, and disable the UMS notification
+ */
+ setMediaStorageNotification(0, 0, 0, false, false, null);
+ updateUsbMassStorageNotification(false);
+ }
+ } else if (newState.equals(Environment.MEDIA_NOFS)) {
+ /*
+ * Storage has no filesystem. Show blank media notification,
+ * and enable UMS notification if connected.
+ */
+ Intent intent = new Intent();
+ intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+ setMediaStorageNotification(
+ com.android.internal.R.string.ext_media_nofs_notification_title,
+ com.android.internal.R.string.ext_media_nofs_notification_message,
+ com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
+ updateUsbMassStorageNotification(mUmsAvailable);
+ } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) {
+ /*
+ * Storage is corrupt. Show corrupt media notification,
+ * and enable UMS notification if connected.
+ */
+ Intent intent = new Intent();
+ intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+ setMediaStorageNotification(
+ com.android.internal.R.string.ext_media_unmountable_notification_title,
+ com.android.internal.R.string.ext_media_unmountable_notification_message,
+ com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
+ updateUsbMassStorageNotification(mUmsAvailable);
+ } else if (newState.equals(Environment.MEDIA_REMOVED)) {
+ /*
+ * Storage has been removed. Show nomedia media notification,
+ * and disable UMS notification regardless of connection state.
+ */
+ setMediaStorageNotification(
+ com.android.internal.R.string.ext_media_nomedia_notification_title,
+ com.android.internal.R.string.ext_media_nomedia_notification_message,
+ com.android.internal.R.drawable.stat_notify_sdcard_usb,
+ true, false, null);
+ updateUsbMassStorageNotification(false);
+ } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) {
+ /*
+ * Storage has been removed unsafely. Show bad removal media notification,
+ * and disable UMS notification regardless of connection state.
+ */
+ setMediaStorageNotification(
+ com.android.internal.R.string.ext_media_badremoval_notification_title,
+ com.android.internal.R.string.ext_media_badremoval_notification_message,
+ com.android.internal.R.drawable.stat_sys_warning,
+ true, true, null);
+ updateUsbMassStorageNotification(false);
+ } else {
+ Log.w(TAG, String.format("Ignoring unknown state {%s}", newState));
+ }
+ }
+
+ /**
+ * Update the state of the USB mass storage notification
+ */
+ void updateUsbMassStorageNotification(boolean available) {
+
+ if (available) {
+ Intent intent = new Intent();
+ intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+ setUsbStorageNotification(
+ com.android.internal.R.string.usb_storage_notification_title,
+ com.android.internal.R.string.usb_storage_notification_message,
+ com.android.internal.R.drawable.stat_sys_data_usb,
+ false, true, pi);
+ } else {
+ setUsbStorageNotification(0, 0, 0, false, false, null);
+ }
+ }
+
+ /**
+ * Sets the USB storage notification.
+ */
+ private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible,
+ PendingIntent pi) {
+
+ if (!visible && mUsbStorageNotification == null) {
+ return;
+ }
+
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (notificationManager == null) {
+ return;
+ }
+
+ if (visible) {
+ Resources r = Resources.getSystem();
+ CharSequence title = r.getText(titleId);
+ CharSequence message = r.getText(messageId);
+
+ if (mUsbStorageNotification == null) {
+ mUsbStorageNotification = new Notification();
+ mUsbStorageNotification.icon = icon;
+ mUsbStorageNotification.when = 0;
+ }
+
+ if (sound) {
+ mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
+ } else {
+ mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
+ }
+
+ mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
+
+ mUsbStorageNotification.tickerText = title;
+ if (pi == null) {
+ Intent intent = new Intent();
+ pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ }
+
+ mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
+ }
+
+ final int notificationId = mUsbStorageNotification.icon;
+ if (visible) {
+ notificationManager.notify(notificationId, mUsbStorageNotification);
+ } else {
+ notificationManager.cancel(notificationId);
+ }
+ }
+
+ private synchronized boolean getMediaStorageNotificationDismissable() {
+ if ((mMediaStorageNotification != null) &&
+ ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
+ Notification.FLAG_AUTO_CANCEL))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Sets the media storage notification.
+ */
+ private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
+ boolean dismissable, PendingIntent pi) {
+
+ if (!visible && mMediaStorageNotification == null) {
+ return;
+ }
+
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (notificationManager == null) {
+ return;
+ }
+
+ if (mMediaStorageNotification != null && visible) {
+ /*
+ * Dismiss the previous notification - we're about to
+ * re-use it.
+ */
+ final int notificationId = mMediaStorageNotification.icon;
+ notificationManager.cancel(notificationId);
+ }
+
+ if (visible) {
+ Resources r = Resources.getSystem();
+ CharSequence title = r.getText(titleId);
+ CharSequence message = r.getText(messageId);
+
+ if (mMediaStorageNotification == null) {
+ mMediaStorageNotification = new Notification();
+ mMediaStorageNotification.when = 0;
+ }
+
+ mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
+
+ if (dismissable) {
+ mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
+ } else {
+ mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
+ }
+
+ mMediaStorageNotification.tickerText = title;
+ if (pi == null) {
+ Intent intent = new Intent();
+ pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ }
+
+ mMediaStorageNotification.icon = icon;
+ mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
+ }
+
+ final int notificationId = mMediaStorageNotification.icon;
+ if (visible) {
+ notificationManager.notify(notificationId, mMediaStorageNotification);
+ } else {
+ notificationManager.cancel(notificationId);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/TetherActivity.java b/core/java/com/android/internal/app/TetherActivity.java
new file mode 100644
index 0000000..cb268b3
--- /dev/null
+++ b/core/java/com/android/internal/app/TetherActivity.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.widget.Toast;
+import android.util.Log;
+
+/**
+ * This activity is shown to the user for him/her to connect/disconnect a Tether
+ * connection. It will display notification when a suitable connection is made
+ * to allow the tether to be setup. A second notification will be show when a
+ * tether is active, allowing the user to manage tethered connections.
+ */
+public class TetherActivity extends AlertActivity implements
+ DialogInterface.OnClickListener {
+
+ private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+
+ /* Used to detect when the USB cable is unplugged, so we can call finish() */
+ private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction() == ConnectivityManager.ACTION_TETHER_STATE_CHANGED) {
+ handleTetherStateChanged(intent);
+ }
+ }
+ };
+
+ private boolean mWantTethering;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // determine if we advertise tethering or untethering
+ ConnectivityManager cm =
+ (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (cm.getTetheredIfaces().length > 0) {
+ mWantTethering = false;
+ } else if (cm.getTetherableIfaces().length > 0) {
+ mWantTethering = true;
+ } else {
+ finish();
+ return;
+ }
+
+ // Set up the "dialog"
+ if (mWantTethering == true) {
+ mAlertParams.mIconId = com.android.internal.R.drawable.ic_dialog_usb;
+ mAlertParams.mTitle = getString(com.android.internal.R.string.tether_title);
+ mAlertParams.mMessage = getString(com.android.internal.R.string.tether_message);
+ mAlertParams.mPositiveButtonText =
+ getString(com.android.internal.R.string.tether_button);
+ mAlertParams.mPositiveButtonListener = this;
+ mAlertParams.mNegativeButtonText =
+ getString(com.android.internal.R.string.tether_button_cancel);
+ mAlertParams.mNegativeButtonListener = this;
+ } else {
+ mAlertParams.mIconId = com.android.internal.R.drawable.ic_dialog_usb;
+ mAlertParams.mTitle = getString(com.android.internal.R.string.tether_stop_title);
+ mAlertParams.mMessage = getString(com.android.internal.R.string.tether_stop_message);
+ mAlertParams.mPositiveButtonText =
+ getString(com.android.internal.R.string.tether_stop_button);
+ mAlertParams.mPositiveButtonListener = this;
+ mAlertParams.mNegativeButtonText =
+ getString(com.android.internal.R.string.tether_stop_button_cancel);
+ mAlertParams.mNegativeButtonListener = this;
+ }
+ setupAlert();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ registerReceiver(mTetherReceiver, new IntentFilter(
+ ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ unregisterReceiver(mTetherReceiver);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onClick(DialogInterface dialog, int which) {
+
+ if (which == POSITIVE_BUTTON) {
+ ConnectivityManager connManager =
+ (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+ // start/stop tethering
+ if (mWantTethering) {
+ if (!connManager.tether("ppp0")) {
+ showTetheringError();
+ }
+ } else {
+ if (!connManager.untether("ppp0")) {
+ showUnTetheringError();
+ }
+ }
+ }
+ // No matter what, finish the activity
+ finish();
+ }
+
+ private void handleTetherStateChanged(Intent intent) {
+ finish();
+ }
+
+ private void showTetheringError() {
+ Toast.makeText(this, com.android.internal.R.string.tether_error_message,
+ Toast.LENGTH_LONG).show();
+ }
+
+ private void showUnTetheringError() {
+ Toast.makeText(this, com.android.internal.R.string.tether_stop_error_message,
+ Toast.LENGTH_LONG).show();
+ }
+
+}
diff --git a/core/java/com/android/internal/app/UsbStorageActivity.java b/core/java/com/android/internal/app/UsbStorageActivity.java
index 34ae2b4..991f04b 100644
--- a/core/java/com/android/internal/app/UsbStorageActivity.java
+++ b/core/java/com/android/internal/app/UsbStorageActivity.java
@@ -25,8 +25,9 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Environment;
-import android.os.IMountService;
-import android.os.MountServiceResultCode;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageResultCode;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -35,6 +36,7 @@
import android.widget.TextView;
import android.widget.Toast;
import android.view.View;
+import android.util.Log;
/**
* This activity is shown to the user for him/her to enable USB mass storage
@@ -42,11 +44,13 @@
* dialog style. It will be launched from a notification.
*/
public class UsbStorageActivity extends Activity {
+ private static final String TAG = "UsbStorageActivity";
private Button mMountButton;
private Button mUnmountButton;
private TextView mBanner;
private TextView mMessage;
private ImageView mIcon;
+ private StorageManager mStorageManager = null;
/** Used to detect when the USB cable is unplugged, so we can call finish() */
private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
@@ -57,11 +61,30 @@
}
}
};
+
+ private StorageEventListener mStorageListener = new StorageEventListener() {
+ @Override
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ if (newState.equals(Environment.MEDIA_SHARED)) {
+ switchDisplay(true);
+ } else {
+ switchDisplay(false);
+ }
+ }
+ };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (mStorageManager == null) {
+ mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
+ if (mStorageManager == null) {
+ Log.w(TAG, "Failed to get StorageManager");
+ }
+ mStorageManager.registerListener(mStorageListener);
+ }
+
setTitle(getString(com.android.internal.R.string.usb_storage_activity_title));
setContentView(com.android.internal.R.layout.usb_storage_activity);
@@ -74,9 +97,11 @@
mMountButton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
- mountAsUsbStorage();
- // TODO: replace with forthcoming MountService callbacks
- switchDisplay(true);
+ int rc = mStorageManager.enableUsbMassStorage();
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, String.format("UMS enable failed (%d)", rc));
+ showSharingError();
+ }
}
});
@@ -84,9 +109,11 @@
mUnmountButton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
- stopUsbStorage();
- // TODO: replace with forthcoming MountService callbacks
- switchDisplay(false);
+ int rc = mStorageManager.disableUsbMassStorage();
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, String.format("UMS disable failed (%d)", rc));
+ showStoppingError();
+ }
}
});
}
@@ -112,19 +139,11 @@
super.onResume();
registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-
- boolean umsOn = false;
try {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService != null) {
- umsOn = mountService.getVolumeShared(
- Environment.getExternalStorageDirectory().getPath(), "ums");
- }
- } catch (android.os.RemoteException exc) {
- // pass
+ switchDisplay(mStorageManager.isUsbMassStorageEnabled());
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to read UMS enable state", ex);
}
- switchDisplay(umsOn);
}
@Override
@@ -134,42 +153,6 @@
unregisterReceiver(mBatteryReceiver);
}
- private void mountAsUsbStorage() {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService == null) {
- showSharingError();
- return;
- }
-
- try {
- if (mountService.shareVolume(
- Environment.getExternalStorageDirectory().getPath(), "ums") !=
- MountServiceResultCode.OperationSucceeded) {
- showSharingError();
- }
- } catch (RemoteException e) {
- showSharingError();
- }
- }
-
- private void stopUsbStorage() {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService == null) {
- showStoppingError();
- return;
- }
-
- try {
- mountService.unshareVolume(
- Environment.getExternalStorageDirectory().getPath(), "ums");
- } catch (RemoteException e) {
- showStoppingError();
- return;
- }
- }
-
private void handleBatteryChanged(Intent intent) {
int pluggedType = intent.getIntExtra("plugged", 0);
if (pluggedType == 0) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e964a8f..dfcc8f7 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -2705,6 +2705,7 @@
mWifiOnTimer = new StopwatchTimer(-3, null, mUnpluggables);
mWifiRunningTimer = new StopwatchTimer(-4, null, mUnpluggables);
mBluetoothOnTimer = new StopwatchTimer(-5, null, mUnpluggables);
+ mAudioOnTimer = new StopwatchTimer(-6, null, mUnpluggables);
mOnBattery = mOnBatteryInternal = false;
mTrackBatteryPastUptime = 0;
mTrackBatteryPastRealtime = 0;
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
new file mode 100644
index 0000000..51f7f69
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.widget;
+
+import java.util.Locale;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Paint.Align;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.util.Log;
+import com.android.internal.R;
+
+/**
+ * A basic, embed-able keyboard designed for password entry. Allows entry of all Latin-1 characters.
+ *
+ * It has two modes: alpha and numeric. In alpha mode, it allows all Latin-1 characters and enables
+ * an additional keyboard with symbols. In numeric mode, it shows a 12-key DTMF dialer-like
+ * keypad with alpha characters hints.
+ */
+public class PasswordEntryKeyboard extends Keyboard {
+ private static final String TAG = "PasswordEntryKeyboard";
+ private static final int SHIFT_OFF = 0;
+ private static final int SHIFT_ON = 1;
+ private static final int SHIFT_LOCKED = 2;
+ public static final int KEYCODE_SPACE = ' ';
+
+ private Drawable mShiftIcon;
+ private Drawable mShiftLockIcon;
+ private Drawable mShiftLockPreviewIcon;
+ private Drawable mOldShiftIcon;
+ private Drawable mOldShiftPreviewIcon;
+ private Drawable mSpaceIcon;
+ private Key mShiftKey;
+ private Key mEnterKey;
+ private Key mF1Key;
+ private Key mSpaceKey;
+ private Locale mLocale;
+ private Resources mRes;
+ private int mExtensionResId;
+ private int mShiftState = SHIFT_OFF;
+
+ static int sSpacebarVerticalCorrection;
+
+ public PasswordEntryKeyboard(Context context, int xmlLayoutResId) {
+ this(context, xmlLayoutResId, 0);
+ }
+
+ public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode) {
+ super(context, xmlLayoutResId, mode);
+ final Resources res = context.getResources();
+ mRes = res;
+ mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift);
+ mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
+ mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
+ mShiftLockPreviewIcon.setBounds(0, 0,
+ mShiftLockPreviewIcon.getIntrinsicWidth(),
+ mShiftLockPreviewIcon.getIntrinsicHeight());
+ mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space);
+ sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
+ R.dimen.password_keyboard_spacebar_vertical_correction);
+ }
+
+ public PasswordEntryKeyboard(Context context, int layoutTemplateResId,
+ CharSequence characters, int columns, int horizontalPadding) {
+ super(context, layoutTemplateResId, characters, columns, horizontalPadding);
+ }
+
+ @Override
+ protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
+ XmlResourceParser parser) {
+ LatinKey key = new LatinKey(res, parent, x, y, parser);
+ final int code = key.codes[0];
+ if (code >=0 && code != '\n' && (code < 32 || code > 127)) {
+ Log.w(TAG, "Key code for " + key.label + " is not latin-1");
+ key.label = " ";
+ key.setEnabled(false);
+ }
+ switch (key.codes[0]) {
+ case 10:
+ mEnterKey = key;
+ break;
+ case PasswordEntryKeyboardView.KEYCODE_F1:
+ mF1Key = key;
+ break;
+ case 32:
+ mSpaceKey = key;
+ break;
+ }
+ return key;
+ }
+
+ /**
+ * Allows enter key resources to be overridden
+ * @param res resources to grab given items from
+ * @param previewId preview drawable shown on enter key
+ * @param iconId normal drawable shown on enter key
+ * @param labelId string shown on enter key
+ */
+ void setEnterKeyResources(Resources res, int previewId, int iconId, int labelId) {
+ if (mEnterKey != null) {
+ // Reset some of the rarely used attributes.
+ mEnterKey.popupCharacters = null;
+ mEnterKey.popupResId = 0;
+ mEnterKey.text = null;
+
+ mEnterKey.iconPreview = res.getDrawable(previewId);
+ mEnterKey.icon = res.getDrawable(iconId);
+ mEnterKey.label = res.getText(labelId);
+
+ // Set the initial size of the preview icon
+ if (mEnterKey.iconPreview != null) {
+ mEnterKey.iconPreview.setBounds(0, 0,
+ mEnterKey.iconPreview.getIntrinsicWidth(),
+ mEnterKey.iconPreview.getIntrinsicHeight());
+ }
+ }
+ }
+
+ /**
+ * Allows shiftlock to be turned on. See {@link #setShiftLocked(boolean)}
+ *
+ */
+ void enableShiftLock() {
+ int index = getShiftKeyIndex();
+ if (index >= 0) {
+ mShiftKey = getKeys().get(index);
+ if (mShiftKey instanceof LatinKey) {
+ ((LatinKey)mShiftKey).enableShiftLock();
+ }
+ mOldShiftIcon = mShiftKey.icon;
+ mOldShiftPreviewIcon = mShiftKey.iconPreview;
+ }
+ }
+
+ /**
+ * Turn on shift lock. This turns on the LED for this key, if it has one.
+ * It should be followed by a call to {@link KeyboardView#invalidateKey(int)}
+ * or {@link KeyboardView#invalidateAllKeys()}
+ *
+ * @param shiftLocked
+ */
+ void setShiftLocked(boolean shiftLocked) {
+ if (mShiftKey != null) {
+ if (shiftLocked) {
+ mShiftKey.on = true;
+ mShiftKey.icon = mShiftLockIcon;
+ mShiftState = SHIFT_LOCKED;
+ } else {
+ mShiftKey.on = false;
+ mShiftKey.icon = mShiftLockIcon;
+ mShiftState = SHIFT_ON;
+ }
+ }
+ }
+
+ /**
+ * Turn on shift mode. Sets shift mode and turns on icon for shift key.
+ * It should be followed by a call to {@link KeyboardView#invalidateKey(int)}
+ * or {@link KeyboardView#invalidateAllKeys()}
+ *
+ * @param shiftLocked
+ */
+ @Override
+ public boolean setShifted(boolean shiftState) {
+ boolean shiftChanged = false;
+ if (mShiftKey != null) {
+ if (shiftState == false) {
+ shiftChanged = mShiftState != SHIFT_OFF;
+ mShiftState = SHIFT_OFF;
+ mShiftKey.on = false;
+ mShiftKey.icon = mOldShiftIcon;
+ } else if (mShiftState == SHIFT_OFF) {
+ shiftChanged = mShiftState == SHIFT_OFF;
+ mShiftState = SHIFT_ON;
+ mShiftKey.on = false;
+ mShiftKey.icon = mShiftIcon;
+ }
+ } else {
+ return super.setShifted(shiftState);
+ }
+ return shiftChanged;
+ }
+
+ /**
+ * Whether or not keyboard is shifted.
+ * @return true if keyboard state is shifted.
+ */
+ @Override
+ public boolean isShifted() {
+ if (mShiftKey != null) {
+ return mShiftState != SHIFT_OFF;
+ } else {
+ return super.isShifted();
+ }
+ }
+
+ /**
+ * Sets keyboard extension. Keyboard extension is shown when input is detected above keyboard
+ * while keyboard has focus.
+ *
+ * @param resId
+ */
+ public void setExtension(int resId) {
+ mExtensionResId = resId;
+ }
+
+ /**
+ * Get current extesion resource id.
+ *
+ * @return resource id, 0 if not set.
+ */
+ public int getExtension() {
+ return mExtensionResId;
+ }
+
+ private void updateSpaceBarForLocale() {
+ if (mLocale != null) {
+ // Create the graphic for spacebar
+ Bitmap buffer = Bitmap.createBitmap(mSpaceKey.width, mSpaceIcon.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(buffer);
+ canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ // TODO: Make the text size a customizable attribute
+ paint.setTextSize(22);
+ paint.setTextAlign(Align.CENTER);
+ // Draw a drop shadow for the text
+ paint.setShadowLayer(1f, 0, 0, 0xFF000000);
+ paint.setColor(0x80C0C0C0);
+ canvas.drawText(mLocale.getDisplayLanguage(mLocale),
+ buffer.getWidth() / 2, - paint.ascent() + 2, paint);
+ int x = (buffer.getWidth() - mSpaceIcon.getIntrinsicWidth()) / 2;
+ int y = buffer.getHeight() - mSpaceIcon.getIntrinsicHeight();
+ mSpaceIcon.setBounds(x, y,
+ x + mSpaceIcon.getIntrinsicWidth(), y + mSpaceIcon.getIntrinsicHeight());
+ mSpaceIcon.draw(canvas);
+ mSpaceKey.icon = new BitmapDrawable(mRes, buffer);
+ mSpaceKey.repeatable = false;
+ } else {
+ mSpaceKey.icon = mRes.getDrawable(R.drawable.sym_keyboard_space);
+ mSpaceKey.repeatable = true;
+ }
+ }
+
+ public void setLanguage(Locale locale) {
+ if (mLocale != null && mLocale.equals(locale)) return;
+ mLocale = locale;
+ updateSpaceBarForLocale();
+ }
+
+ static class LatinKey extends Keyboard.Key {
+ private boolean mShiftLockEnabled;
+ private boolean mEnabled = true;
+
+ public LatinKey(Resources res, Keyboard.Row parent, int x, int y,
+ XmlResourceParser parser) {
+ super(res, parent, x, y, parser);
+ if (popupCharacters != null && popupCharacters.length() == 0) {
+ // If there is a keyboard with no keys specified in popupCharacters
+ popupResId = 0;
+ }
+ }
+
+ void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ void enableShiftLock() {
+ mShiftLockEnabled = true;
+ }
+
+ @Override
+ public void onReleased(boolean inside) {
+ if (!mShiftLockEnabled) {
+ super.onReleased(inside);
+ } else {
+ pressed = !pressed;
+ }
+ }
+
+ /**
+ * Overriding this method so that we can reduce the target area for certain keys.
+ */
+ @Override
+ public boolean isInside(int x, int y) {
+ if (!mEnabled) {
+ return false;
+ }
+ final int code = codes[0];
+ if (code == KEYCODE_SHIFT || code == KEYCODE_DELETE) {
+ y -= height / 10;
+ if (code == KEYCODE_SHIFT) x += width / 6;
+ if (code == KEYCODE_DELETE) x -= width / 6;
+ } else if (code == KEYCODE_SPACE) {
+ y += PasswordEntryKeyboard.sSpacebarVerticalCorrection;
+ }
+ return super.isInside(x, y);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
new file mode 100644
index 0000000..b809afc
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.Selection;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewRoot;
+import android.view.inputmethod.InputConnection;
+import android.widget.EditText;
+import com.android.internal.R;
+
+public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
+
+ public static final int KEYBOARD_MODE_ALPHA = 0;
+ public static final int KEYBOARD_MODE_NUMERIC = 1;
+ private static final int KEYBOARD_STATE_NORMAL = 0;
+ private static final int KEYBOARD_STATE_SHIFTED = 1;
+ private static final int KEYBOARD_STATE_CAPSLOCK = 2;
+ private static final String TAG = "PasswordEntryKeyboardHelper";
+ private int mKeyboardMode = KEYBOARD_MODE_ALPHA;
+ private int mKeyboardState = KEYBOARD_STATE_NORMAL;
+ private PasswordEntryKeyboard mQwertyKeyboard;
+ private PasswordEntryKeyboard mQwertyKeyboardShifted;
+ private PasswordEntryKeyboard mSymbolsKeyboard;
+ private PasswordEntryKeyboard mSymbolsKeyboardShifted;
+ private PasswordEntryKeyboard mNumericKeyboard;
+ private Context mContext;
+ private View mTargetView;
+ private KeyboardView mKeyboardView;
+
+ public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) {
+ mContext = context;
+ mTargetView = targetView;
+ mKeyboardView = keyboardView;
+ createKeyboards();
+ mKeyboardView.setOnKeyboardActionListener(this);
+ }
+
+ public boolean isAlpha() {
+ return mKeyboardMode == KEYBOARD_MODE_ALPHA;
+ }
+
+ private void createKeyboards() {
+ mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric);
+ mQwertyKeyboard = new PasswordEntryKeyboard(mContext,
+ R.xml.password_kbd_qwerty, R.id.mode_normal);
+ mQwertyKeyboard.enableShiftLock();
+
+ mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext,
+ R.xml.password_kbd_qwerty_shifted,
+ R.id.mode_normal);
+ mQwertyKeyboardShifted.enableShiftLock();
+ mQwertyKeyboardShifted.setShifted(true); // always shifted.
+
+ mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_symbols);
+ mSymbolsKeyboard.enableShiftLock();
+
+ mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext,
+ R.xml.password_kbd_symbols_shift);
+ mSymbolsKeyboardShifted.enableShiftLock();
+ mSymbolsKeyboardShifted.setShifted(true); // always shifted
+ }
+
+ public void setKeyboardMode(int mode) {
+ switch (mode) {
+ case KEYBOARD_MODE_ALPHA:
+ mKeyboardView.setKeyboard(mQwertyKeyboard);
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ break;
+ case KEYBOARD_MODE_NUMERIC:
+ mKeyboardView.setKeyboard(mNumericKeyboard);
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ break;
+ }
+ mKeyboardMode = mode;
+ }
+
+ private void sendKeyEventsToTarget(int keyEventCode) {
+ Handler handler = mTargetView.getHandler();
+ KeyEvent[] events = KeyCharacterMap.load(KeyCharacterMap.ALPHA).getEvents(
+ new char[] { (char) keyEventCode });
+ if (events != null) {
+ for (KeyEvent event : events) {
+ handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY, event));
+ }
+ }
+ }
+
+ public void sendDownUpKeyEvents(int keyEventCode) {
+ long eventTime = SystemClock.uptimeMillis();
+ Handler handler = mTargetView.getHandler();
+ handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ }
+
+ public void onKey(int primaryCode, int[] keyCodes) {
+ Log.v(TAG, "Key code = " + Integer.toHexString(primaryCode));
+ if (primaryCode == Keyboard.KEYCODE_DELETE) {
+ handleBackspace();
+ } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
+ handleShift();
+ } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
+ handleClose();
+ return;
+ } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE && mKeyboardView != null) {
+ handleModeChange();
+ } else {
+ handleCharacter(primaryCode, keyCodes);
+ // Switch back to old keyboard if we're not in capslock mode
+ if (mKeyboardState == KEYBOARD_STATE_SHIFTED) {
+ // skip to the unlocked state
+ mKeyboardState = KEYBOARD_STATE_CAPSLOCK;
+ handleShift();
+ }
+ }
+ }
+
+ private void handleModeChange() {
+ final Keyboard current = mKeyboardView.getKeyboard();
+ Keyboard next = null;
+ if (current == mQwertyKeyboard || current == mQwertyKeyboardShifted) {
+ next = mSymbolsKeyboard;
+ } else if (current == mSymbolsKeyboard || current == mSymbolsKeyboardShifted) {
+ next = mQwertyKeyboard;
+ }
+ if (next != null) {
+ mKeyboardView.setKeyboard(next);
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ }
+ }
+
+ private void handleBackspace() {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ }
+
+ private void handleShift() {
+ if (mKeyboardView == null) {
+ return;
+ }
+ Keyboard current = mKeyboardView.getKeyboard();
+ PasswordEntryKeyboard next = null;
+ final boolean isAlphaMode = current == mQwertyKeyboard
+ || current == mQwertyKeyboardShifted;
+ if (mKeyboardState == KEYBOARD_STATE_NORMAL) {
+ mKeyboardState = isAlphaMode ? KEYBOARD_STATE_SHIFTED : KEYBOARD_STATE_CAPSLOCK;
+ next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted;
+ } else if (mKeyboardState == KEYBOARD_STATE_SHIFTED) {
+ mKeyboardState = KEYBOARD_STATE_CAPSLOCK;
+ next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted;
+ } else if (mKeyboardState == KEYBOARD_STATE_CAPSLOCK) {
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ next = isAlphaMode ? mQwertyKeyboard : mSymbolsKeyboard;
+ }
+ if (next != null) {
+ if (next != current) {
+ mKeyboardView.setKeyboard(next);
+ }
+ next.setShiftLocked(mKeyboardState == KEYBOARD_STATE_CAPSLOCK);
+ mKeyboardView.setShifted(mKeyboardState != KEYBOARD_STATE_NORMAL);
+ }
+ }
+
+ private void handleCharacter(int primaryCode, int[] keyCodes) {
+ // Maybe turn off shift if not in capslock mode.
+ if (mKeyboardView.isShifted() && primaryCode != ' ' && primaryCode != '\n') {
+ primaryCode = Character.toUpperCase(primaryCode);
+ }
+ sendKeyEventsToTarget(primaryCode);
+ }
+
+ private void handleClose() {
+
+ }
+
+ public void onPress(int primaryCode) {
+
+ }
+
+ public void onRelease(int primaryCode) {
+
+ }
+
+ public void onText(CharSequence text) {
+
+ }
+
+ public void swipeDown() {
+
+ }
+
+ public void swipeLeft() {
+
+ }
+
+ public void swipeRight() {
+
+ }
+
+ public void swipeUp() {
+
+ }
+};
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
new file mode 100644
index 0000000..9b93fc2
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.widget.PopupWindow;
+import com.android.internal.R;
+
+public class PasswordEntryKeyboardView extends KeyboardView {
+
+ public static final int KEYCODE_OPTIONS = -100;
+ static final int KEYCODE_SHIFT_LONGPRESS = -101;
+ static final int KEYCODE_VOICE = -102;
+ static final int KEYCODE_F1 = -103;
+ static final int KEYCODE_NEXT_LANGUAGE = -104;
+
+ private boolean mExtensionVisible;
+ private PasswordEntryKeyboardView mExtension;
+ private PopupWindow mExtensionPopup;
+ private boolean mFirstEvent;
+
+ public PasswordEntryKeyboardView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent me) {
+ if (((PasswordEntryKeyboard) getKeyboard()).getExtension() == 0) {
+ return super.onTouchEvent(me);
+ }
+ if (me.getY() < 0) {
+ if (mExtensionVisible) {
+ int action = me.getAction();
+ if (mFirstEvent) action = MotionEvent.ACTION_DOWN;
+ mFirstEvent = false;
+ MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
+ action,
+ me.getX(), me.getY() + mExtension.getHeight(), me.getMetaState());
+ boolean result = mExtension.onTouchEvent(translated);
+ translated.recycle();
+ if (me.getAction() == MotionEvent.ACTION_UP
+ || me.getAction() == MotionEvent.ACTION_CANCEL) {
+ closeExtension();
+ }
+ return result;
+ } else {
+ if (openExtension()) {
+ MotionEvent cancel = MotionEvent.obtain(me.getDownTime(), me.getEventTime(),
+ MotionEvent.ACTION_CANCEL, me.getX() - 100, me.getY() - 100, 0);
+ super.onTouchEvent(cancel);
+ cancel.recycle();
+ if (mExtension.getHeight() > 0) {
+ MotionEvent translated = MotionEvent.obtain(me.getEventTime(),
+ me.getEventTime(),
+ MotionEvent.ACTION_DOWN,
+ me.getX(), me.getY() + mExtension.getHeight(),
+ me.getMetaState());
+ mExtension.onTouchEvent(translated);
+ translated.recycle();
+ } else {
+ mFirstEvent = true;
+ }
+ }
+ return true;
+ }
+ } else if (mExtensionVisible) {
+ closeExtension();
+ // Send a down event into the main keyboard first
+ MotionEvent down = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
+ MotionEvent.ACTION_DOWN, me.getX(), me.getY(), me.getMetaState());
+ super.onTouchEvent(down);
+ down.recycle();
+ // Send the actual event
+ return super.onTouchEvent(me);
+ } else {
+ return super.onTouchEvent(me);
+ }
+ }
+
+ private boolean openExtension() {
+ if (((PasswordEntryKeyboard) getKeyboard()).getExtension() == 0) return false;
+ makePopupWindow();
+ mExtensionVisible = true;
+ return true;
+ }
+
+ private void makePopupWindow() {
+ if (mExtensionPopup == null) {
+ int[] windowLocation = new int[2];
+ mExtensionPopup = new PopupWindow(getContext());
+ mExtensionPopup.setBackgroundDrawable(null);
+ LayoutInflater li = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ mExtension = (PasswordEntryKeyboardView) li.inflate(
+ R.layout.password_keyboard_input, null);
+ mExtension.setOnKeyboardActionListener(getOnKeyboardActionListener());
+ mExtension.setPopupParent(this);
+ mExtension.setPopupOffset(0, -windowLocation[1]);
+ Keyboard keyboard;
+ mExtension.setKeyboard(keyboard = new PasswordEntryKeyboard(getContext(),
+ ((PasswordEntryKeyboard) getKeyboard()).getExtension()));
+ mExtensionPopup.setContentView(mExtension);
+ mExtensionPopup.setWidth(getWidth());
+ mExtensionPopup.setHeight(keyboard.getHeight());
+ getLocationInWindow(windowLocation);
+ // TODO: Fix the "- 30".
+ mExtension.setPopupOffset(0, -windowLocation[1] - 30);
+ mExtensionPopup.showAtLocation(this, 0, 0, -keyboard.getHeight()
+ + windowLocation[1]);
+ } else {
+ mExtension.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void closing() {
+ super.closing();
+ if (mExtensionPopup != null && mExtensionPopup.isShowing()) {
+ mExtensionPopup.dismiss();
+ mExtensionPopup = null;
+ }
+ }
+
+ private void closeExtension() {
+ mExtension.setVisibility(INVISIBLE);
+ mExtension.closing();
+ mExtensionVisible = false;
+ }
+}
diff --git a/core/java/com/android/internal/widget/WeightedLinearLayout.java b/core/java/com/android/internal/widget/WeightedLinearLayout.java
index b90204e..3d09f08 100644
--- a/core/java/com/android/internal/widget/WeightedLinearLayout.java
+++ b/core/java/com/android/internal/widget/WeightedLinearLayout.java
@@ -52,7 +52,8 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
- final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+ final int screenWidth = metrics.widthPixels;
+ final boolean isPortrait = screenWidth < metrics.heightPixels;
final int widthMode = getMode(widthMeasureSpec);
@@ -62,14 +63,13 @@
int height = getMeasuredHeight();
boolean measure = false;
- final int widthSize = getSize(widthMeasureSpec);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, EXACTLY);
final float widthWeight = isPortrait ? mMinorWeight : mMajorWeight;
if (widthMode == AT_MOST && widthWeight > 0.0f) {
- if (width < (widthSize * widthWeight)) {
- widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * widthWeight),
+ if (width < (screenWidth * widthWeight)) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (screenWidth * widthWeight),
EXACTLY);
measure = true;
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 7fd58e8..1ffd265 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -70,7 +70,6 @@
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
- android_util_Base64.cpp \
android/graphics/Bitmap.cpp \
android/graphics/BitmapFactory.cpp \
android/graphics/Camera.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1d22de3..7c8df03 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -153,7 +153,6 @@
extern int register_android_server_BluetoothA2dpService(JNIEnv* env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env);
-extern int register_android_util_Base64(JNIEnv* env);
extern int register_android_location_GpsLocationProvider(JNIEnv* env);
extern int register_android_backup_BackupDataInput(JNIEnv *env);
extern int register_android_backup_BackupDataOutput(JNIEnv *env);
@@ -1266,7 +1265,6 @@
REG_JNI(register_android_server_BluetoothA2dpService),
REG_JNI(register_android_message_digest_sha1),
REG_JNI(register_android_ddm_DdmHandleNativeHeap),
- REG_JNI(register_android_util_Base64),
REG_JNI(register_android_location_GpsLocationProvider),
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 020aff4a..c197010 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -143,12 +143,68 @@
if (handle != NULL) sqlite3_close(handle);
}
+void sqlTrace(void *databaseName, const char *sql) {
+ LOGI("sql_statement|%s|%s\n", (char *)databaseName, sql);
+}
+
+/* public native void enableSqlTracing(); */
+static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName)
+{
+ sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+ char const *path = env->GetStringUTFChars(databaseName, NULL);
+ if (path == NULL) {
+ LOGE("Failure in enableSqlTracing(). VM ran out of memory?\n");
+ return; // VM would have thrown OutOfMemoryError
+ }
+ int len = strlen(path);
+ char *traceFuncArg = (char *)malloc(len + 1);
+ strncpy(traceFuncArg, path, len);
+ traceFuncArg[len-1] = NULL;
+ env->ReleaseStringUTFChars(databaseName, path);
+ sqlite3_trace(handle, &sqlTrace, (void *)traceFuncArg);
+ LOGI("will be printing all sql statements executed on database = %s\n", traceFuncArg);
+}
+
+void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) {
+ double d = tm/1000000.0;
+ LOGI("elapsedTime4Sql|%s|%.3f ms|%s\n", (char *)databaseName, d, sql);
+}
+
+/* public native void enableSqlProfiling(); */
+static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName)
+{
+ sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+ char const *path = env->GetStringUTFChars(databaseName, NULL);
+ if (path == NULL) {
+ LOGE("Failure in enableSqlProfiling(). VM ran out of memory?\n");
+ return; // VM would have thrown OutOfMemoryError
+ }
+ int len = strlen(path);
+ char *traceFuncArg = (char *)malloc(len + 1);
+ strncpy(traceFuncArg, path, len);
+ traceFuncArg[len-1] = NULL;
+ env->ReleaseStringUTFChars(databaseName, path);
+ sqlite3_profile(handle, &sqlProfile, (void *)traceFuncArg);
+ LOGI("will be printing execution time of all sql statements executed on database = %s\n",
+ traceFuncArg);
+}
+
/* public native void close(); */
static void dbclose(JNIEnv* env, jobject object)
{
sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
if (handle != NULL) {
+ // release the memory associated with the traceFuncArg in enableSqlTracing function
+ void *traceFuncArg = sqlite3_trace(handle, &sqlTrace, NULL);
+ if (traceFuncArg != NULL) {
+ free(traceFuncArg);
+ }
+ // release the memory associated with the traceFuncArg in enableSqlProfiling function
+ traceFuncArg = sqlite3_profile(handle, &sqlProfile, NULL);
+ if (traceFuncArg != NULL) {
+ free(traceFuncArg);
+ }
LOGV("Closing database: handle=%p\n", handle);
int result = sqlite3_close(handle);
if (result == SQLITE_OK) {
@@ -357,6 +413,8 @@
/* name, signature, funcPtr */
{"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen},
{"dbclose", "()V", (void *)dbclose},
+ {"enableSqlTracing", "(Ljava/lang/String;)V", (void *)enableSqlTracing},
+ {"enableSqlProfiling", "(Ljava/lang/String;)V", (void *)enableSqlProfiling},
{"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL},
{"lastInsertRow", "()J", (void *)lastInsertRow},
{"lastChangeCount", "()I", (void *)lastChangeCount},
diff --git a/core/jni/android_util_Base64.cpp b/core/jni/android_util_Base64.cpp
deleted file mode 100644
index bc69747..0000000
--- a/core/jni/android_util_Base64.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-/* //device/libs/android_runtime/android_util_Base64.cpp
-**
-** 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.
-*/
-
-/*********************************************************
-*
-* This code was copied from
-* system/extra/ssh/dropbear-0.49/libtomcrypt/src/misc/base64/base64_decode.c
-*
-*********************************************************/
-
-#define LOG_TAG "Base64"
-
-#include <utils/Log.h>
-
-#include <android_runtime/AndroidRuntime.h>
-
-#include "JNIHelp.h"
-
-#include <sys/errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <signal.h>
-
-namespace android {
-
-static const unsigned char map[256] = {
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
-255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6,
- 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
- 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
-255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
- 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
- 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255 };
-
-/**
- base64 decode a block of memory
- @param in The base64 data to decode
- @param inlen The length of the base64 data
- @param out [out] The destination of the binary decoded data
- @param outlen [in/out] The max size and resulting size of the decoded data
- @return 0 if successful
-*/
-int base64_decode(const unsigned char *in, unsigned long inlen,
- unsigned char *out, unsigned long *outlen)
-{
- unsigned long t, x, y, z;
- unsigned char c;
- int g;
-
- g = 3;
- for (x = y = z = t = 0; x < inlen; x++) {
- c = map[in[x]&0xFF];
- if (c == 255) continue;
- /* the final = symbols are read and used to trim the remaining bytes */
- if (c == 254) {
- c = 0;
- /* prevent g < 0 which would potentially allow an overflow later */
- if (--g < 0) {
- return -3;
- }
- } else if (g != 3) {
- /* we only allow = to be at the end */
- return -4;
- }
-
- t = (t<<6)|c;
-
- if (++y == 4) {
- if (z + g > *outlen) {
- return -2;
- }
- out[z++] = (unsigned char)((t>>16)&255);
- if (g > 1) out[z++] = (unsigned char)((t>>8)&255);
- if (g > 2) out[z++] = (unsigned char)(t&255);
- y = t = 0;
- }
- }
- if (y != 0) {
- return -5;
- }
- *outlen = z;
- return 0;
-}
-
-static jbyteArray decodeBase64(JNIEnv *env, jobject jobj, jstring jdata)
-{
- const char * rawData = env->GetStringUTFChars(jdata, NULL);
- int stringLength = env->GetStringUTFLength(jdata);
-
- int resultLength = stringLength / 4 * 3;
- if (rawData[stringLength-1] == '=') {
- resultLength -= 1;
- if (rawData[stringLength-2] == '=') {
- resultLength -= 1;
- }
- }
-
- jbyteArray byteArray = env->NewByteArray(resultLength);
- jbyte* byteArrayData = env->GetByteArrayElements(byteArray, NULL);
-
- unsigned long outlen = resultLength;
- int result = base64_decode((const unsigned char*)rawData, stringLength, (unsigned char *)byteArrayData, &outlen);
- if (result != 0)
- memset((unsigned char *)byteArrayData, -result, resultLength);
-
- env->ReleaseStringUTFChars(jdata, rawData);
- env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
-
- return byteArray;
-}
-
-static const JNINativeMethod methods[] = {
- {"decodeBase64Native", "(Ljava/lang/String;)[B", (void*)decodeBase64 }
-};
-
-static const char* const kBase64PathName = "android/os/Base64Utils";
-
-int register_android_util_Base64(JNIEnv* env)
-{
- jclass clazz;
-
- clazz = env->FindClass(kBase64PathName);
- LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Base64Utils");
-
- return AndroidRuntime::registerNativeMethods(
- env, kBase64PathName,
- methods, NELEM(methods));
-}
-
-}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 665088a..713e7258 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -679,6 +679,12 @@
android:label="@string/permlab_setWallpaperHints"
android:description="@string/permdesc_setWallpaperHints" />
+ <!-- Allows applications to set the system time -->
+ <permission android:name="android.permission.SET_TIME"
+ android:protectionLevel="signatureOrSystem"
+ android:label="@string/permlab_setTime"
+ android:description="@string/permdesc_setTime" />
+
<!-- Allows applications to set the system time zone -->
<permission android:name="android.permission.SET_TIME_ZONE"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
@@ -1239,6 +1245,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name="com.android.internal.app.TetherActivity"
+ android:theme="@style/Theme.Dialog.Alert"
+ android:excludeFromRecents="true">
+ </activity>
<activity android:name="com.android.internal.app.UsbStorageActivity"
android:excludeFromRecents="true">
</activity>
diff --git a/core/res/res/drawable-en-hdpi/sym_keyboard_delete.png b/core/res/res/drawable-en-hdpi/sym_keyboard_delete.png
new file mode 100755
index 0000000..569369e
--- /dev/null
+++ b/core/res/res/drawable-en-hdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png
new file mode 100755
index 0000000..ca76375
--- /dev/null
+++ b/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-en-mdpi/sym_keyboard_delete.png b/core/res/res/drawable-en-mdpi/sym_keyboard_delete.png
new file mode 100644
index 0000000..f1f7c58c
--- /dev/null
+++ b/core/res/res/drawable-en-mdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-en-mdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-en-mdpi/sym_keyboard_feedback_delete.png
new file mode 100644
index 0000000..3c90839
--- /dev/null
+++ b/core/res/res/drawable-en-mdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
new file mode 100644
index 0000000..b6c234c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_off.9.png
new file mode 100644
index 0000000..9f3c087
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_on.9.png
new file mode 100644
index 0000000..4041342
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
new file mode 100644
index 0000000..73a8cd1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
new file mode 100644
index 0000000..8473e8e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
new file mode 100644
index 0000000..f4f59c0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_off.9.png
new file mode 100644
index 0000000..1508653
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_on.9.png
new file mode 100644
index 0000000..66c231a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_off.9.png
new file mode 100644
index 0000000..cdad182
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_on.9.png
new file mode 100644
index 0000000..e95f4cf
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_search_category_default.png b/core/res/res/drawable-hdpi/ic_search_category_default.png
index 4038129..f78234e 100644
--- a/core/res/res/drawable-hdpi/ic_search_category_default.png
+++ b/core/res/res/drawable-hdpi/ic_search_category_default.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/password_field_default.9.png b/core/res/res/drawable-hdpi/password_field_default.9.png
new file mode 100644
index 0000000..2c424f0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/password_field_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/search_source_selector_indicator.png b/core/res/res/drawable-hdpi/search_source_selector_indicator.png
deleted file mode 100644
index b93a0c0..0000000
--- a/core/res/res/drawable-hdpi/search_source_selector_indicator.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_secure.png b/core/res/res/drawable-hdpi/stat_sys_secure.png
new file mode 100644
index 0000000..4bae258
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_active.png b/core/res/res/drawable-hdpi/stat_sys_tether_active.png
new file mode 100755
index 0000000..4c14c07
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_active.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_usb.png b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png
new file mode 100755
index 0000000..4c14c07
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_delete.png b/core/res/res/drawable-hdpi/sym_keyboard_delete.png
new file mode 100755
index 0000000..59d78be
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_delete_dim.png b/core/res/res/drawable-hdpi/sym_keyboard_delete_dim.png
new file mode 100644
index 0000000..34b6d1f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_delete_dim.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_delete.png
new file mode 100755
index 0000000..ca76375
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_ok.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_ok.png
new file mode 100644
index 0000000..2d144ec
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_ok.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_return.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_return.png
new file mode 100755
index 0000000..ae57299
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_return.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift.png
new file mode 100755
index 0000000..4db31c8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png
new file mode 100755
index 0000000..3fd5659
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_space.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_space.png
new file mode 100755
index 0000000..98266ee
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_space.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num0_no_plus.png b/core/res/res/drawable-hdpi/sym_keyboard_num0_no_plus.png
new file mode 100644
index 0000000..2aad23c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num0_no_plus.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num1.png b/core/res/res/drawable-hdpi/sym_keyboard_num1.png
new file mode 100755
index 0000000..0fc03ef
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num2.png b/core/res/res/drawable-hdpi/sym_keyboard_num2.png
new file mode 100755
index 0000000..283560b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num3.png b/core/res/res/drawable-hdpi/sym_keyboard_num3.png
new file mode 100755
index 0000000..9a3b329
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num4.png b/core/res/res/drawable-hdpi/sym_keyboard_num4.png
new file mode 100755
index 0000000..f13ff1a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num5.png b/core/res/res/drawable-hdpi/sym_keyboard_num5.png
new file mode 100755
index 0000000..c251329
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num5.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num6.png b/core/res/res/drawable-hdpi/sym_keyboard_num6.png
new file mode 100755
index 0000000..4acba4c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num6.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num7.png b/core/res/res/drawable-hdpi/sym_keyboard_num7.png
new file mode 100755
index 0000000..14931c1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num7.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num8.png b/core/res/res/drawable-hdpi/sym_keyboard_num8.png
new file mode 100755
index 0000000..d4973fd
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num8.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num9.png b/core/res/res/drawable-hdpi/sym_keyboard_num9.png
new file mode 100755
index 0000000..49cec66
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_ok.png b/core/res/res/drawable-hdpi/sym_keyboard_ok.png
new file mode 100644
index 0000000..9105da2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_ok.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_ok_dim.png b/core/res/res/drawable-hdpi/sym_keyboard_ok_dim.png
new file mode 100644
index 0000000..bd419de
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_ok_dim.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_return.png b/core/res/res/drawable-hdpi/sym_keyboard_return.png
new file mode 100755
index 0000000..58505c5
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_return.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_shift.png b/core/res/res/drawable-hdpi/sym_keyboard_shift.png
new file mode 100755
index 0000000..8149081
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_shift.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_shift_locked.png b/core/res/res/drawable-hdpi/sym_keyboard_shift_locked.png
new file mode 100755
index 0000000..31ca277
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_space.png b/core/res/res/drawable-hdpi/sym_keyboard_space.png
new file mode 100755
index 0000000..3e98b30
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_space.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/usb_android.png b/core/res/res/drawable-hdpi/usb_android.png
index 8153ec4..f6f899a 100644
--- a/core/res/res/drawable-hdpi/usb_android.png
+++ b/core/res/res/drawable-hdpi/usb_android.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/usb_android_connected.png b/core/res/res/drawable-hdpi/usb_android_connected.png
index 6449b7c..583ca00 100644
--- a/core/res/res/drawable-hdpi/usb_android_connected.png
+++ b/core/res/res/drawable-hdpi/usb_android_connected.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png
new file mode 100644
index 0000000..20f3d50
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_off.9.png
new file mode 100644
index 0000000..d09ce53
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_on.9.png
new file mode 100644
index 0000000..a9e008c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png
new file mode 100644
index 0000000..1ed3065
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
new file mode 100644
index 0000000..5710ebf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
new file mode 100644
index 0000000..dd7d89e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_off.9.png
new file mode 100644
index 0000000..77426ef
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_on.9.png
new file mode 100644
index 0000000..e4c9bd5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_off.9.png
new file mode 100644
index 0000000..bb98b01
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_on.9.png
new file mode 100644
index 0000000..3c7dcc80
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_search_category_default.png b/core/res/res/drawable-mdpi/ic_search_category_default.png
index 7eea584a..94446db 100755
--- a/core/res/res/drawable-mdpi/ic_search_category_default.png
+++ b/core/res/res/drawable-mdpi/ic_search_category_default.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/password_field_default.9.png b/core/res/res/drawable-mdpi/password_field_default.9.png
new file mode 100644
index 0000000..3193275
--- /dev/null
+++ b/core/res/res/drawable-mdpi/password_field_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/search_source_selector_indicator.png b/core/res/res/drawable-mdpi/search_source_selector_indicator.png
deleted file mode 100644
index 26bf18a..0000000
--- a/core/res/res/drawable-mdpi/search_source_selector_indicator.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_secure.png b/core/res/res/drawable-mdpi/stat_sys_secure.png
new file mode 100644
index 0000000..5f9ae69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_active.png b/core/res/res/drawable-mdpi/stat_sys_tether_active.png
new file mode 100644
index 0000000..2d0da4c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_tether_active.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_usb.png b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000..2d0da4c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_delete.png b/core/res/res/drawable-mdpi/sym_keyboard_delete.png
new file mode 100644
index 0000000..43a033ea
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_delete_dim.png b/core/res/res/drawable-mdpi/sym_keyboard_delete_dim.png
new file mode 100644
index 0000000..25460d8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_delete_dim.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_delete.png
new file mode 100644
index 0000000..1edb10b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_ok.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_ok.png
new file mode 100644
index 0000000..3148836
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_ok.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_return.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_return.png
new file mode 100644
index 0000000..03d9c9b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_return.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift.png
new file mode 100644
index 0000000..97f4661
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift_locked.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift_locked.png
new file mode 100755
index 0000000..7194b30
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_space.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_space.png
new file mode 100644
index 0000000..739db68
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_space.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num0_no_plus.png b/core/res/res/drawable-mdpi/sym_keyboard_num0_no_plus.png
new file mode 100644
index 0000000..91332b1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num0_no_plus.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num1.png b/core/res/res/drawable-mdpi/sym_keyboard_num1.png
new file mode 100644
index 0000000..aaac11b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num2.png b/core/res/res/drawable-mdpi/sym_keyboard_num2.png
new file mode 100644
index 0000000..4372eb8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num3.png b/core/res/res/drawable-mdpi/sym_keyboard_num3.png
new file mode 100644
index 0000000..6f54c85
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num3.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num4.png b/core/res/res/drawable-mdpi/sym_keyboard_num4.png
new file mode 100644
index 0000000..3e50bb9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num4.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num5.png b/core/res/res/drawable-mdpi/sym_keyboard_num5.png
new file mode 100644
index 0000000..c39ef44
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num5.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num6.png b/core/res/res/drawable-mdpi/sym_keyboard_num6.png
new file mode 100644
index 0000000..ea88ceb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num6.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num7.png b/core/res/res/drawable-mdpi/sym_keyboard_num7.png
new file mode 100644
index 0000000..ce800ba
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num7.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num8.png b/core/res/res/drawable-mdpi/sym_keyboard_num8.png
new file mode 100644
index 0000000..1a8ff94
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num8.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num9.png b/core/res/res/drawable-mdpi/sym_keyboard_num9.png
new file mode 100644
index 0000000..8b344c0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_ok.png b/core/res/res/drawable-mdpi/sym_keyboard_ok.png
new file mode 100644
index 0000000..b8b5292
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_ok.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_ok_dim.png b/core/res/res/drawable-mdpi/sym_keyboard_ok_dim.png
new file mode 100644
index 0000000..33ecff5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_ok_dim.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_return.png b/core/res/res/drawable-mdpi/sym_keyboard_return.png
new file mode 100644
index 0000000..17f2574
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_return.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_shift.png b/core/res/res/drawable-mdpi/sym_keyboard_shift.png
new file mode 100644
index 0000000..0566e5a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_shift.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_shift_locked.png b/core/res/res/drawable-mdpi/sym_keyboard_shift_locked.png
new file mode 100755
index 0000000..ccaf05d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_space.png b/core/res/res/drawable-mdpi/sym_keyboard_space.png
new file mode 100644
index 0000000..4e6273b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_space.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/usb_android.png b/core/res/res/drawable-mdpi/usb_android.png
new file mode 100644
index 0000000..df1afbb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/usb_android.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/usb_android_connected.png b/core/res/res/drawable-mdpi/usb_android_connected.png
new file mode 100644
index 0000000..fca77a7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/usb_android_connected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_keyboard_key_fulltrans.xml b/core/res/res/drawable/btn_keyboard_key_fulltrans.xml
new file mode 100644
index 0000000..cfad6e3
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_fulltrans.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Toggle keys. Use checkable/checked state. -->
+
+ <item android:state_checkable="true" android:state_checked="true"
+ android:state_pressed="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed_on" />
+ <item android:state_checkable="true" android:state_pressed="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed_off" />
+ <item android:state_checkable="true" android:state_checked="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_normal_on" />
+ <item android:state_checkable="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_normal_off" />
+
+ <!-- Normal keys -->
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
+ <item android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/search_source_selector_background.xml b/core/res/res/drawable/search_source_selector_background.xml
deleted file mode 100644
index fcacd89..0000000
--- a/core/res/res/drawable/search_source_selector_background.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- TODO: Need focused and pressed backgrounds -->
-
-</selector>
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 231e11c..7ae68f9 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -28,8 +28,8 @@
android:paddingBottom="3dip"
android:paddingLeft="3dip"
android:paddingRight="1dip"
- android:majorWeight="0.5"
- android:minorWeight="0.8">
+ android:majorWeight="0.65"
+ android:minorWeight="0.9">
<LinearLayout android:id="@+id/topPanel"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
index 5bc034b..b089df6 100644
--- a/core/res/res/layout/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -16,105 +16,68 @@
** limitations under the License.
*/
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@android:color/background_dark">
-
- <!-- displays dots as user enters pin -->
- <LinearLayout android:id="@+id/pinDisplayGroup"
- android:orientation="horizontal"
- android:layout_centerInParent="true"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:addStatesFromChildren="true"
- android:gravity="center_vertical"
- android:baselineAligned="false"
- android:paddingRight="0dip"
- android:layout_marginRight="30dip"
- android:layout_marginLeft="30dip"
- android:layout_marginTop="6dip"
- android:background="@android:drawable/edit_text">
-
- <EditText android:id="@+id/pinDisplay"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="fill_parent"
- android:maxLines="1"
- android:background="@null"
- android:textSize="32sp"
- android:inputType="textPassword"
- />
-
- <ImageButton android:id="@+id/backspace"
- android:src="@android:drawable/ic_input_delete"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_marginTop="2dip"
- android:layout_marginRight="2dip"
- android:layout_marginBottom="2dip"
- android:gravity="center"
- />
-
- </LinearLayout>
-
- <!-- header text ('Enter Pin Code') -->
- <TextView android:id="@+id/headerText"
- android:layout_above="@id/pinDisplayGroup"
- android:layout_centerHorizontal="true"
- android:layout_marginBottom="30dip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="24sp"
- />
+ android:background="#70000000"
+ android:gravity="center_horizontal">
<LinearLayout
- android:orientation="horizontal"
- android:layout_alignParentBottom="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="8dip"
- android:layout_marginLeft="8dip"
- android:layout_marginRight="8dip">
-
- <Button android:id="@+id/ok"
- android:text="@android:string/ok"
- android:layout_alignParentBottom="true"
+ android:orientation="horizontal">
+ <!-- "Enter PIN(Password) to unlock" -->
+ <TextView android:id="@+id/enter_password_label"
android:layout_width="0dip"
android:layout_height="wrap_content"
- android:layout_weight="1.0"
- android:layout_marginBottom="8dip"
- android:layout_marginRight="8dip"
- android:textSize="18sp"
- />
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ android:layout_marginRight="6dip"
+ android:layout_marginLeft="6dip"
+ android:layout_marginTop="10dip"
+ android:layout_marginBottom="10dip"
+ android:gravity="center"
+ android:ellipsize="marquee"
+ android:text="@android:string/keyguard_password_enter_password_code"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
- <Button android:id="@+id/emergencyCall"
- android:text="@android:string/lockscreen_emergency_call"
- android:layout_alignParentBottom="true"
- android:layout_centerHorizontal="true"
+ <!-- Password entry field -->
+ <EditText android:id="@+id/passwordEntry"
android:layout_width="0dip"
android:layout_height="wrap_content"
- android:layout_weight="1.0"
- android:layout_marginBottom="8dip"
- android:layout_marginLeft="8dip"
- android:textSize="18sp"
- android:drawableLeft="@drawable/ic_emergency"
- android:drawablePadding="8dip"
+ android:layout_weight="1"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:textSize="32sp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:background="@drawable/password_field_default"
+ android:textColor="#ffffffff"
/>
</LinearLayout>
- <!-- Not currently visible on this screen -->
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
+ <!-- Alphanumeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_alignParentBottom="true"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_marginTop="6dip"
- android:layout_alignParentRight="true"
- android:layout_marginRight="8dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:visibility="gone"
+ android:background="#00000000"
+ android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
/>
-</RelativeLayout>
+ <!-- emergency call button -->
+ <Button
+ android:id="@+id/emergencyCall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/ic_emergency"
+ android:drawablePadding="8dip"
+ android:text="@string/lockscreen_emergency_call"
+ style="@style/Widget.Button.Transparent"
+ />
+
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml
index a7814af..9ee8781 100644
--- a/core/res/res/layout/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -17,142 +17,77 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
android:background="#70000000"
android:gravity="center_horizontal">
- <LinearLayout android:id="@+id/topDisplayGroup"
- android:layout_width="fill_parent"
+ <!-- "Enter PIN(Password) to unlock" -->
+ <TextView android:id="@+id/enter_password_label"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <RelativeLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
-
- <com.android.internal.widget.DigitalClock android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="6dip"
- android:layout_marginLeft="6dip"
- android:layout_alignParentTop="true"
- android:layout_alignParentLeft="true">
-
- <TextView android:id="@+id/timeDisplay"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="bottom"
- android:singleLine="true"
- android:ellipsize="none"
- android:textSize="56sp"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:shadowColor="#C0000000"
- android:shadowDx="0"
- android:shadowDy="0"
- android:shadowRadius="3.0"
- android:layout_marginBottom="6dip"
- />
-
- <TextView android:id="@+id/am_pm"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:gravity="bottom"
- android:singleLine="true"
- android:ellipsize="none"
- android:textSize="18sp"
- android:layout_marginLeft="4dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:shadowColor="#C0000000"
- android:shadowDx="0"
- android:shadowDy="0"
- android:shadowRadius="3.0"
- />
-
- </com.android.internal.widget.DigitalClock>
-
- <TextView android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_marginTop="6dip"
- android:layout_alignParentRight="true"
- android:layout_marginRight="8dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
-
- </RelativeLayout>
-
- <!-- password entry -->
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_marginRight="6dip"
- android:layout_marginLeft="6dip"
- android:gravity="center_vertical"
- android:hint="@android:string/keyguard_password_enter_password_code"
- android:background="@android:drawable/edit_text">
-
- <!-- displays dots as user enters pin -->
- <TextView android:id="@+id/pinDisplay"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:maxLines="1"
- android:textAppearance="?android:attr/textAppearanceLargeInverse"
- android:textStyle="bold"
- android:inputType="textPassword"
- />
-
- <ImageButton android:id="@+id/backspace"
- android:src="@android:drawable/ic_input_delete"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="-3dip"
- android:layout_marginBottom="-3dip"
- />
- </LinearLayout>
-
- </LinearLayout>
-
- <include
- android:id="@+id/keyPad"
- layout="@android:layout/twelve_key_entry"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/topDisplayGroup"
+ android:orientation="horizontal"
+ android:layout_marginRight="6dip"
+ android:layout_marginLeft="6dip"
android:layout_marginTop="10dip"
+ android:layout_marginBottom="10dip"
+ android:gravity="center"
+ android:text="@android:string/keyguard_password_enter_password_code"
+ android:textAppearance="?android:attr/textAppearanceLarge"
/>
- <!-- spacer below keypad -->
+ <!-- spacer above text entry field -->
<View
android:id="@+id/spacerBottom"
android:layout_width="fill_parent"
android:layout_height="1dip"
android:layout_marginTop="6dip"
- android:layout_above="@id/emergencyCall"
android:background="@android:drawable/divider_horizontal_dark"
/>
- <!-- The emergency button should take the rest of the space and be centered vertically -->
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
+ <!-- Password entry field -->
+ <EditText android:id="@+id/passwordEntry"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:inputType="textPassword"
android:gravity="center"
- android:orientation="vertical">
+ android:layout_gravity="center"
+ android:textSize="32sp"
+ android:layout_marginTop="15dip"
+ android:layout_marginLeft="30dip"
+ android:layout_marginRight="30dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:background="@drawable/password_field_default"
+ android:textColor="#ffffffff"
+ />
- <!-- emergency call button -->
- <Button
- android:id="@+id/emergencyCall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableLeft="@android:drawable/ic_emergency"
- android:drawablePadding="8dip"
- android:text="@android:string/lockscreen_emergency_call"
- />
- </LinearLayout>
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+ <!-- Alphanumeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_alignParentBottom="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#00000000"
+ android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+ />
+
+ <!-- emergency call button -->
+ <Button
+ android:id="@+id/emergencyCall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/ic_emergency"
+ android:drawablePadding="8dip"
+ android:layout_marginTop="20dip"
+ android:layout_marginBottom="20dip"
+ android:text="@string/lockscreen_emergency_call"
+ style="@style/Widget.Button.Transparent"
+ />
</LinearLayout>
diff --git a/core/res/res/layout/password_keyboard_input.xml b/core/res/res/layout/password_keyboard_input.xml
new file mode 100755
index 0000000..e40b69e
--- /dev/null
+++ b/core/res/res/layout/password_keyboard_input.xml
@@ -0,0 +1,29 @@
+<?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.
+*/
+-->
+
+<com.android.passwordunlockdemo.LatinKeyboardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/keyboardView"
+ android:layout_alignParentBottom="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#00000000"
+ android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+ />
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index 12285fd..cf246ba 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -55,11 +55,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
-
- <include android:id="@+id/search_source_selector"
- layout="@layout/search_source_selector"
+
+ <ImageView
+ android:id="@+id/search_app_icon"
+ android:layout_height="36dip"
+ android:layout_width="36dip"
android:layout_marginRight="7dip"
- android:layout_gravity="center_vertical" />
+ android:layout_gravity="center_vertical"
+ />
<view class="android.app.SearchDialog$SearchAutoComplete"
android:id="@+id/search_src_text"
diff --git a/core/res/res/layout/search_source_selector.xml b/core/res/res/layout/search_source_selector.xml
deleted file mode 100644
index c69dfc0..0000000
--- a/core/res/res/layout/search_source_selector.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="48dip"
- android:layout_height="match_parent"
- android:foreground="@drawable/search_source_selector_indicator"
- android:foregroundGravity="bottom|right"
- >
-
- <ImageButton
- android:id="@+id/search_source_selector_icon"
- android:background="@drawable/search_source_selector_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerInside"
- android:focusable="true"
- android:clickable="true"
- />
-
-</FrameLayout>
diff --git a/core/res/res/values-cs/donottranslate-cldr.xml b/core/res/res/values-cs/donottranslate-cldr.xml
index e90e53c..a026734 100644
--- a/core/res/res/values-cs/donottranslate-cldr.xml
+++ b/core/res/res/values-cs/donottranslate-cldr.xml
@@ -144,6 +144,6 @@
<string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string>
<string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s. %2$s - %6$s %8$s. %7$s %9$s</string>
<string name="short_format_month">%b</string>
- <string name="full_wday_month_day_no_year">EEEE MMMM d</string>
+ <string name="full_wday_month_day_no_year">EEEE, d. MMMM</string>
<string name="abbrev_wday_month_day_year">E d. MMMM yyyy</string>
</resources>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
new file mode 100644
index 0000000..647a562
--- /dev/null
+++ b/core/res/res/values-land/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<resources>
+ <dimen name="password_keyboard_key_height">47dip</dimen>
+ <dimen name="password_keyboard_spacebar_vertical_correction">2dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 3dbfa25..cdc15c2 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -107,6 +107,7 @@
icons in the status bar that are not notifications. -->
<string-array name="status_bar_icon_order">
<item><xliff:g id="id">clock</xliff:g></item>
+ <item><xliff:g id="id">secure</xliff:g></item>
<item><xliff:g id="id">alarm_clock</xliff:g></item>
<item><xliff:g id="id">battery</xliff:g></item>
<item><xliff:g id="id">phone_signal</xliff:g></item>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7728c50..54781e3 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -923,6 +923,19 @@
<attr name="name" />
</declare-styleable>
+ <!-- Private tag to declare the original package name that this package is
+ based on. Only used for packages installed in the system image. If
+ given, and different than the actual package name, and the given
+ original package was previously installed on the device but the new
+ one was not, then the data for the old one will be renamed to be
+ for the new package.
+
+ <p>This appears as a child tag of the root
+ {@link #AndroidManifest manifest} tag. -->
+ <declare-styleable name="AndroidManifestOriginalPackage" parent="AndroidManifest">
+ <attr name="name" />
+ </declare-styleable>
+
<!-- The <code>provider</code> tag declares a
{@link android.content.ContentProvider} class that is available
as part of the package's application components, supplying structured
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2b8ddc4..46d6352 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -237,4 +237,8 @@
<!-- Component name of the service providing geocoder API support. -->
<string name="config_geocodeProvider">@null</string>
+
+ <!-- Flag indicating whether headset events are used by kernel to indicate
+ TTY mode changes. -->
+ <bool name="tty_mode_uses_headset_events">false</bool>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6a3538d..5cea28db 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -4,16 +4,16 @@
**
** 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
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
**
-** http://www.apache.org/licenses/LICENSE-2.0
+** http://www.apache.org/licenses/LICENSE-2.0
**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
+** 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.
*/
-->
@@ -34,4 +34,8 @@
<dimen name="fastscroll_thumb_width">64dp</dimen>
<!-- Height of the fastscroll thumb -->
<dimen name="fastscroll_thumb_height">52dp</dimen>
+ <!-- Default height of a key in the password keyboard -->
+ <dimen name="password_keyboard_key_height">56dip</dimen>
+ <!-- Default correction for the space key in the password keyboard -->
+ <dimen name="password_keyboard_spacebar_vertical_correction">4dip</dimen>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 30d0da7..89cbd08 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1017,6 +1017,12 @@
configuration, and installed applications.</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_setTime">set time</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_setTime">Allows an application to change
+ the phone\'s clock time.</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_setTimeZone">set time zone</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_setTimeZone">Allows an application to change
@@ -1078,7 +1084,13 @@
<string name="permlab_changeNetworkState">change network connectivity</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_changeNetworkState">Allows an application to change
- the state network connectivity.</string>
+ the state of network connectivity.</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_changeTetherState">change tethered connectivity</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the applicaiton to do this. -->
+ <string name="permdesc_changeTetherState">Allows an application to change
+ the state of tethered network connectivity.</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_changeBackgroundDataSetting">change background data usage setting</string>
@@ -1524,6 +1536,14 @@
<!-- Displayed on lock screen's right tab - turn sound off -->
<string name="lockscreen_sound_off_label">Sound off</string>
+ <!-- Password keyboard strings. Used by LockScreen and Settings --><skip />
+ <!-- Label for "switch to symbols" key. Must be short to fit on key! -->
+ <string name="password_keyboard_label_symbol_key">\?123</string>
+ <!-- Label for "switch to alphabetic" key. Must be short to fit on key! -->
+ <string name="password_keyboard_label_alpha_key">ABC</string>
+ <!-- Label for ALT modifier key. Must be short to fit on key! -->
+ <string name="password_keyboard_label_alt_key">ALT</string>
+
<!-- A format string for 12-hour time of day, just the hour, not the minute, with lower-case "am" or "pm" (example: "3pm"). -->
<string name="hour_ampm">"<xliff:g id="hour" example="3">%-l</xliff:g><xliff:g id="ampm" example="pm">%P</xliff:g>"</string>
@@ -2200,4 +2220,40 @@
Used by AccessibilityService to announce the purpose of the view.
-->
<string name="description_star">favorite</string>
+
+
+ <!-- Strings for Tethering dialogs -->
+ <!-- This is the label for the activity, and should never be visible to the user. -->
+ <!-- See TETHERING. TETHERING_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to Tether. This is the title. -->
+ <string name="tether_title">USB tethering available</string>
+ <!-- See TETHER. This is the message. -->
+ <string name="tether_message">Select \"Tether\" if you want to share your phone\'s data connection with your computer.</string>
+ <!-- See TETHER. This is the button text to Tether the computer with the phone. -->
+ <string name="tether_button">Tether</string>
+ <!-- See TETHER. This is the button text to ignore the plugging in of the phone.. -->
+ <string name="tether_button_cancel">Cancel</string>
+ <!-- See TETHER. If there was an error mounting, this is the text. -->
+ <string name="tether_error_message">There is a problem tethering.</string>
+ <!-- TETHER: When the user connects the phone to a computer, we show a notification asking if he wants to share his cellular network connection. This is the title -->
+ <string name="tether_available_notification_title">USB tethering available</string>
+ <!-- See USB_STORAGE. This is the message. -->
+ <string name="tether_available_notification_message">Select to tether your computer to your phone.</string>
+ <!-- TETHER_STOP: While TETHER is enabled, we show a notification dialog asking if he wants to stop. This is the title -->
+ <string name="tether_stop_notification_title">Untether</string>
+ <!-- See TETHER. This is the message. -->
+ <string name="tether_stop_notification_message">Select to untether your computer.</string>
+
+ <!-- TETHER stop dialog strings -->
+ <!-- This is the label for the activity, and should never be visible to the user. -->
+ <!-- See TETHER_STOP. TETHER_STOP_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to stop tethering. This is the title. -->
+ <string name="tether_stop_title">Disconnect tethering</string>
+ <!-- See TETHER_STOP. This is the message. -->
+ <string name="tether_stop_message">You have been sharing your phone\'s cellular data connection with your computer. Select \"Disconnect\" to disconnect USB tethering.</string>
+ <!-- See TETHER_STOP. This is the button text to disconnect tethering. -->
+ <string name="tether_stop_button">Disconnect</string>
+ <!-- See TETHER_STOP. This is the button text to cancel disconnecting the tether. -->
+ <string name="tether_stop_button_cancel">Cancel</string>
+ <!-- See TETHER_STOP_DIALOG. If there was an error disconnect, this is the text. -->
+ <string name="tether_stop_error_message">We\'ve encountered a problem turning off Tethering. Please try again.</string>
+
</resources>
diff --git a/core/res/res/xml-land/password_kbd_qwerty.xml b/core/res/res/xml-land/password_kbd_qwerty.xml
new file mode 100755
index 0000000..a3d4e88
--- /dev/null
+++ b/core/res/res/xml-land/password_kbd_qwerty.xml
@@ -0,0 +1,89 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="-" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="=" />
+ <Key android:codes="46" android:keyLabel="."
+ android:keyWidth="10%p"/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+ android:iconPreview="@drawable/sym_keyboard_feedback_return"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml-land/password_kbd_qwerty_shifted.xml b/core/res/res/xml-land/password_kbd_qwerty_shifted.xml
new file mode 100755
index 0000000..2285d91
--- /dev/null
+++ b/core/res/res/xml-land/password_kbd_qwerty_shifted.xml
@@ -0,0 +1,88 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="_" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="+" />
+ <Key android:codes="46" android:keyLabel="."/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+ android:iconPreview="@drawable/sym_keyboard_feedback_return"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty.xml b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
new file mode 100755
index 0000000..a3d4e88
--- /dev/null
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
@@ -0,0 +1,89 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="-" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="=" />
+ <Key android:codes="46" android:keyLabel="."
+ android:keyWidth="10%p"/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+ android:iconPreview="@drawable/sym_keyboard_feedback_return"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
new file mode 100755
index 0000000..2285d91
--- /dev/null
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
@@ -0,0 +1,88 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="_" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="+" />
+ <Key android:codes="46" android:keyLabel="."/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+ android:iconPreview="@drawable/sym_keyboard_feedback_return"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml/password_kbd_extension.xml b/core/res/res/xml/password_kbd_extension.xml
new file mode 100755
index 0000000..354594e
--- /dev/null
+++ b/core/res/res/xml/password_kbd_extension.xml
@@ -0,0 +1,61 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row android:rowEdgeFlags="top">
+ <Key android:keyLabel="!" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="\@"/>
+ <Key android:keyLabel="\#"/>
+ <Key android:keyLabel="&"/>
+ <Key android:keyLabel="-"/>
+ <Key android:keyLabel="\'"/>
+ <Key android:keyLabel=":"/>
+ <Key android:keyLabel="""/>
+ <Key android:keyLabel="/"/>
+ <Key android:keyLabel="\?" android:keyEdgeFlags="right"
+ />
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"
+ />
+ <Key android:codes="50" android:keyLabel="2"
+ />
+ <Key android:codes="51" android:keyLabel="3"
+ />
+ <Key android:codes="52" android:keyLabel="4"
+ />
+ <Key android:codes="53" android:keyLabel="5"
+ />
+ <Key android:codes="54" android:keyLabel="6"/>
+ <Key android:codes="55" android:keyLabel="7"
+ />
+ <Key android:codes="56" android:keyLabel="8"/>
+ <Key android:codes="57" android:keyLabel="9"/>
+ <Key android:codes="48" android:keyLabel="0"
+ android:keyEdgeFlags="right"/>
+ </Row>
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_numeric.xml b/core/res/res/xml/password_kbd_numeric.xml
new file mode 100755
index 0000000..e3f1612
--- /dev/null
+++ b/core/res/res/xml/password_kbd_numeric.xml
@@ -0,0 +1,53 @@
+<?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.
+*/
+-->
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="33.33%p"
+ android:horizontalGap="2px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="49" android:keyIcon="@drawable/sym_keyboard_num1" android:keyEdgeFlags="left"/>
+ <Key android:codes="50" android:keyIcon="@drawable/sym_keyboard_num2"/>
+ <Key android:codes="51" android:keyIcon="@drawable/sym_keyboard_num3"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="52" android:keyIcon="@drawable/sym_keyboard_num4" android:keyEdgeFlags="left"/>
+ <Key android:codes="53" android:keyIcon="@drawable/sym_keyboard_num5"/>
+ <Key android:codes="54" android:keyIcon="@drawable/sym_keyboard_num6"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="55" android:keyIcon="@drawable/sym_keyboard_num7" android:keyEdgeFlags="left"/>
+ <Key android:codes="56" android:keyIcon="@drawable/sym_keyboard_num8"/>
+ <Key android:codes="57" android:keyIcon="@drawable/sym_keyboard_num9"/>
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"/>
+ <Key android:codes="48" android:keyIcon="@drawable/sym_keyboard_num0_no_plus"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_popup_template.xml b/core/res/res/xml/password_kbd_popup_template.xml
new file mode 100644
index 0000000..5ddfd3e
--- /dev/null
+++ b/core/res/res/xml/password_kbd_popup_template.xml
@@ -0,0 +1,27 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_qwerty.xml b/core/res/res/xml/password_kbd_qwerty.xml
new file mode 100755
index 0000000..d4a454b
--- /dev/null
+++ b/core/res/res/xml/password_kbd_qwerty.xml
@@ -0,0 +1,102 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row android:rowEdgeFlags="top">
+ <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"/>
+ <Key android:codes="50" android:keyLabel="2"/>
+ <Key android:codes="51" android:keyLabel="3"/>
+ <Key android:codes="52" android:keyLabel="4"/>
+ <Key android:codes="53" android:keyLabel="5"/>
+ <Key android:codes="54" android:keyLabel="6"/>
+ <Key android:codes="55" android:keyLabel="7"/>
+ <Key android:codes="56" android:keyLabel="8"/>
+ <Key android:codes="57" android:keyLabel="9"/>
+ <Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="-" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="=" />
+ <Key android:codes="46" android:keyLabel="."
+ android:keyWidth="10%p"/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml/password_kbd_qwerty_shifted.xml b/core/res/res/xml/password_kbd_qwerty_shifted.xml
new file mode 100755
index 0000000..f341d9e
--- /dev/null
+++ b/core/res/res/xml/password_kbd_qwerty_shifted.xml
@@ -0,0 +1,101 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row android:rowEdgeFlags="top">
+ <Key android:codes="64" android:keyLabel="\@" android:keyEdgeFlags="left"/>
+ <Key android:codes="35" android:keyLabel="\#"/>
+ <Key android:codes="36" android:keyLabel="$"/>
+ <Key android:codes="37" android:keyLabel="%"/>
+ <Key android:codes="38" android:keyLabel="&"/>
+ <Key android:codes="42" android:keyLabel="*"/>
+ <Key android:codes="45" android:keyLabel="-"/>
+ <Key android:keyLabel="+"/>
+ <Key android:codes="40" android:keyLabel="("/>
+ <Key android:codes="41" android:keyLabel=")" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="_" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="+" />
+ <Key android:codes="46" android:keyLabel="."/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml/password_kbd_symbols.xml b/core/res/res/xml/password_kbd_symbols.xml
new file mode 100755
index 0000000..c97e6ae
--- /dev/null
+++ b/core/res/res/xml/password_kbd_symbols.xml
@@ -0,0 +1,91 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"/>
+ <Key android:codes="50" android:keyLabel="2"/>
+ <Key android:codes="51" android:keyLabel="3"/>
+ <Key android:codes="52" android:keyLabel="4"/>
+ <Key android:codes="53" android:keyLabel="5"/>
+ <Key android:codes="54" android:keyLabel="6"/>
+ <Key android:codes="55" android:keyLabel="7"/>
+ <Key android:codes="56" android:keyLabel="8"/>
+ <Key android:codes="57" android:keyLabel="9"/>
+ <Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="64" android:keyLabel="\@" android:keyEdgeFlags="left"/>
+ <Key android:codes="35" android:keyLabel="\#"/>
+ <Key android:codes="36" android:keyLabel="$"/>
+ <Key android:codes="37" android:keyLabel="%"/>
+ <Key android:codes="38" android:keyLabel="&"/>
+ <Key android:codes="42" android:keyLabel="*"/>
+ <Key android:codes="45" android:keyLabel="-"/>
+ <Key android:keyLabel="+"/>
+ <Key android:codes="40" android:keyLabel="("
+ android:popupKeyboard="@xml/password_kbd_popup_template"
+ android:popupCharacters="[{<"
+ />
+ <Key android:codes="41" android:keyLabel=")" android:keyEdgeFlags="right"
+ android:popupKeyboard="@xml/password_kbd_popup_template"
+ android:popupCharacters="]}>"
+ />
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyLabel="@string/password_keyboard_label_alt_key"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="33" android:keyLabel="!"/>
+ <Key android:codes="34" android:keyLabel="""/>
+ <Key android:codes="39" android:keyLabel="\'"/>
+ <Key android:codes="58" android:keyLabel=":"/>
+ <Key android:codes="59" android:keyLabel=";"/>
+ <Key android:codes="47" android:keyLabel="/" />
+ <Key android:codes="63" android:keyLabel="\?"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_alpha_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," android:keyWidth="10%p"/>
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:keyWidth="40%p"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:isRepeatable="true"/>
+ <Key android:keyLabel="." android:keyWidth="10%p" />
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ />
+ </Row>
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_symbols_shift.xml b/core/res/res/xml/password_kbd_symbols_shift.xml
new file mode 100755
index 0000000..97ec3c5f
--- /dev/null
+++ b/core/res/res/xml/password_kbd_symbols_shift.xml
@@ -0,0 +1,87 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:keyLabel="~" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="`"/>
+ <Key android:keyLabel="|"/>
+ <Key android:keyLabel="•"/>
+ <Key android:keyLabel="√"/>
+ <Key android:keyLabel="π"/>
+ <Key android:keyLabel="÷"/>
+ <Key android:keyLabel="×"/>
+ <Key android:keyLabel="{"/>
+ <Key android:keyLabel="}" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="¥" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="£"/>
+ <Key android:keyLabel="¢"/>
+ <Key android:keyLabel="€"/>
+ <Key android:keyLabel="°"/>
+ <Key android:keyLabel="^"/>
+ <Key android:keyLabel="_"/>
+ <Key android:keyLabel="="/>
+ <Key android:keyLabel="["/>
+ <Key android:keyLabel="]" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyLabel="@string/password_keyboard_label_alt_key"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="™"/>
+ <Key android:keyLabel="®"/>
+ <Key android:keyLabel="©"/>
+ <Key android:keyLabel="¶"/>
+ <Key android:keyLabel="\\"/>
+ <Key android:keyLabel="<"/>
+ <Key android:keyLabel=">"/>
+ <Key android:codes="-5"
+ android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"
+ />
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_alpha_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="„" android:keyWidth="10%p" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:keyWidth="40%p"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:isRepeatable="true"/>
+ <Key android:keyLabel="…" android:keyWidth="10%p" />
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ />
+ </Row>
+</Keyboard>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 8d7e187..6bc6f2e 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -74,6 +74,10 @@
<uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_TRANSITION_TYPES" />
<uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_NOTIFICATION_TYPES" />
+ <!-- package manager test permissions -->
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.DELETE_PACKAGES" />
+
<application android:theme="@style/Theme">
<uses-library android:name="android.test.runner" />
<activity android:name="StubTestBrowserActivity" android:label="Stubbed Test Browser">
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index 533338e..1505a7c 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -18,16 +18,23 @@
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
+import android.test.suitebuilder.annotation.SmallTest;
import android.test.mock.MockContext;
import android.test.mock.MockContentResolver;
import android.accounts.Account;
+import android.os.Bundle;
+
+import java.util.List;
+import java.io.File;
public class SyncStorageEngineTest extends AndroidTestCase {
/**
* Test that we handle the case of a history row being old enough to purge before the
* correcponding sync is finished. This can happen if the clock changes while we are syncing.
+ *
*/
+ @SmallTest
public void testPurgeActiveSync() throws Exception {
final Account account = new Account("a@example.com", "example.type");
final String authority = "testprovider";
@@ -41,7 +48,150 @@
long historyId = engine.insertStartSyncEvent(
account, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
- engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
+ engine.stopSyncEvent(historyId, new Bundle(), time1 - time0, "yay", 0, 0);
+ }
+
+ /**
+ * Test that we can create, remove and retrieve periodic syncs
+ */
+ @SmallTest
+ public void testPeriodics() throws Exception {
+ final Account account1 = new Account("a@example.com", "example.type");
+ final Account account2 = new Account("b@example.com", "example.type.2");
+ final String authority = "testprovider";
+ final Bundle extras1 = new Bundle();
+ extras1.putString("a", "1");
+ final Bundle extras2 = new Bundle();
+ extras2.putString("a", "2");
+ final int period1 = 200;
+ final int period2 = 1000;
+
+ PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
+ PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ removePeriodicSyncs(engine, account1, authority);
+ removePeriodicSyncs(engine, account2, authority);
+
+ // this should add two distinct periodic syncs for account1 and one for account2
+ engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
+ engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
+ engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
+ engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority);
+
+ assertEquals(2, syncs.size());
+
+ assertEquals(sync1, syncs.get(0));
+ assertEquals(sync3, syncs.get(1));
+
+ engine.removePeriodicSync(sync1.account, sync1.authority, sync1.extras);
+
+ syncs = engine.getPeriodicSyncs(account1, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account2, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync4, syncs.get(0));
+ }
+
+ private void removePeriodicSyncs(SyncStorageEngine engine, Account account, String authority) {
+ engine.setIsSyncable(account, authority, engine.getIsSyncable(account, authority));
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority);
+ for (PeriodicSync sync : syncs) {
+ engine.removePeriodicSync(sync.account, sync.authority, sync.extras);
+ }
+ }
+
+ @SmallTest
+ public void testAuthorityPersistence() throws Exception {
+ final Account account1 = new Account("a@example.com", "example.type");
+ final Account account2 = new Account("b@example.com", "example.type.2");
+ final String authority1 = "testprovider1";
+ final String authority2 = "testprovider2";
+ final Bundle extras1 = new Bundle();
+ extras1.putString("a", "1");
+ final Bundle extras2 = new Bundle();
+ extras2.putString("a", "2");
+ extras2.putLong("b", 2);
+ extras2.putInt("c", 1);
+ extras2.putBoolean("d", true);
+ extras2.putDouble("e", 1.2);
+ extras2.putFloat("f", 4.5f);
+ extras2.putParcelable("g", account1);
+ final int period1 = 200;
+ final int period2 = 1000;
+
+ PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1);
+ PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2);
+ PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ removePeriodicSyncs(engine, account1, authority1);
+ removePeriodicSyncs(engine, account2, authority1);
+ removePeriodicSyncs(engine, account1, authority2);
+ removePeriodicSyncs(engine, account2, authority2);
+
+ engine.setMasterSyncAutomatically(false);
+
+ engine.setIsSyncable(account1, authority1, 1);
+ engine.setSyncAutomatically(account1, authority1, true);
+
+ engine.setIsSyncable(account2, authority1, 1);
+ engine.setSyncAutomatically(account2, authority1, true);
+
+ engine.setIsSyncable(account1, authority2, 1);
+ engine.setSyncAutomatically(account1, authority2, false);
+
+ engine.setIsSyncable(account2, authority2, 0);
+ engine.setSyncAutomatically(account2, authority2, true);
+
+ engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
+ engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
+ engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
+ engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
+ engine.addPeriodicSync(sync5.account, sync5.authority, sync5.extras, sync5.period);
+
+ engine.writeAllState();
+ engine.clearAndReadState();
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority1);
+ assertEquals(2, syncs.size());
+ assertEquals(sync1, syncs.get(0));
+ assertEquals(sync2, syncs.get(1));
+
+ syncs = engine.getPeriodicSyncs(account1, authority2);
+ assertEquals(2, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+ assertEquals(sync4, syncs.get(1));
+
+ syncs = engine.getPeriodicSyncs(account2, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync5, syncs.get(0));
+
+ assertEquals(true, engine.getSyncAutomatically(account1, authority1));
+ assertEquals(true, engine.getSyncAutomatically(account2, authority1));
+ assertEquals(false, engine.getSyncAutomatically(account1, authority2));
+ assertEquals(true, engine.getSyncAutomatically(account2, authority2));
+
+ assertEquals(1, engine.getIsSyncable(account1, authority1));
+ assertEquals(1, engine.getIsSyncable(account2, authority1));
+ assertEquals(1, engine.getIsSyncable(account1, authority2));
+ assertEquals(0, engine.getIsSyncable(account2, authority2));
}
}
@@ -49,15 +199,26 @@
ContentResolver mResolver;
+ private final Context mRealContext;
+
public TestContext(ContentResolver resolver, Context realContext) {
super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
+ mRealContext = realContext;
mResolver = resolver;
}
@Override
+ public File getFilesDir() {
+ return mRealContext.getFilesDir();
+ }
+
+ @Override
public void enforceCallingOrSelfPermission(String permission, String message) {
}
+ @Override
+ public void sendBroadcast(Intent intent) {
+ }
@Override
public ContentResolver getContentResolver() {
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDebugTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDebugTest.java
deleted file mode 100644
index ea807bd..0000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDebugTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.
- */
-
-package android.database.sqlite;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for the SQLiteDebug
- */
-public class SQLiteDebugTest extends TestCase {
- private static final String TEST_DB = "test.db";
-
- public void testCaptureSql() {
- String rslt = SQLiteDebug.captureSql(TEST_DB, "select * from t1 where a=? and b=1",
- new Object[] {"blah"});
- String expectedVal = "select * from t1 where a='blah' and b=1";
- assertTrue(rslt.equals("captured_sql|" + TEST_DB + "|" + expectedVal));
-
- rslt = SQLiteDebug.captureSql(TEST_DB, "select * from t1 where a=?",
- new Object[] {"blah"});
- expectedVal = "select * from t1 where a='blah'";
- assertTrue(rslt.equals("captured_sql|" + TEST_DB + "|" + expectedVal));
-
- rslt = SQLiteDebug.captureSql(TEST_DB, "select * from t1 where a=1",
- new Object[] {"blah"});
- assertTrue(rslt.startsWith("too many bindArgs provided."));
-
- rslt = SQLiteDebug.captureSql(TEST_DB, "update t1 set a=? where b=?",
- new Object[] {"blah", "foo"});
- expectedVal = "update t1 set a='blah' where b='foo'";
- assertTrue(rslt.equals("captured_sql|" + TEST_DB + "|" + expectedVal));
- }
-}
diff --git a/docs/html/guide/topics/ui/themes.jd b/docs/html/guide/topics/ui/themes.jd
index 03995126..de699f2 100644
--- a/docs/html/guide/topics/ui/themes.jd
+++ b/docs/html/guide/topics/ui/themes.jd
@@ -7,143 +7,328 @@
<div id="qv">
<h2>In this document</h2>
<ol>
- <li><a href="#styles">Styles</a></li>
- <li><a href="#themes">Themes</a>
+ <li><a href="#DefiningStyles">Defining Styles</a>
<ol>
- <li><a href="#inTheManifest">Set the theme in the manifest</a></li>
- <li><a href="#fromTheApp">Set the theme from the application</a></li>
+ <li><a href="#Inheritance">Inheritance</a></li>
+ <li><a href="#Properties">Style Properties</a></li>
</ol>
</li>
+ <li><a href="#ApplyingStyles">Applying Styles and Themes to the UI</a>
+ <ol>
+ <li><a href="#ApplyAStyle">Apply a style to a View</a></li>
+ <li><a href="#ApplyATheme">Apply a theme to an Activity or application</a></li>
+ </ol>
+ </li>
+ <li><a href="#PlatformStyles">Using Platform Styles and Themes</a></li>
</ol>
<h2>See also</h2>
<ol>
- <li><a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style and Theme Resources</a></li>
+ <li><a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
+ and Theme Resources</a></li>
+ <li>{@link android.R.style} for Android styles and themes</li>
+ <li>{@link android.R.attr} for all style attributes</li>
</ol>
</div>
</div>
-<p>When designing your application, you can use <em>styles</em> and <em>themes</em> to apply uniform formatting to its various screens and UI elements.
-<ul>
- <li>A style is a set of one or more formatting attributes that you can apply as a unit to single elements in your layout XML file(s). For example, you could define a style that specifies a certain text size and color, then apply it to instances of a certain type of View element.</li>
- <li>A theme is a set of one or more formatting attributes that you can apply as a unit to all activities in an application or just a single activity. For example, you could define a theme that sets specific colors for the window frame and the panel foreground and background, and sets text sizes and colors for menus, then apply it to the activities of your application.</li>
-</ul>
+<p>A <strong>style</strong> is a collection of properties that
+specify the look and format for a {@link android.view.View} or window.
+A style can specify properties such as height, padding, font color, font size,
+background color, and much more. A style is defined in an XML resource that is
+separate from the XML that specifies the layout.</p>
-<p>Styles and themes are resources. Android provides some default style and theme resources that you can use, or you can declare your own custom style and theme resources.</p>
+<p>Styles in Android share a similar philosophy to cascading stylesheets in web
+design—they allow you to separate the design from the
+content.</p>
-<p>To create custom styles and themes:</p>
-<ol>
- <li>Create a file named <code>styles.xml</code> in the your application's <code>res/values</code> directory. Add a root <code><resources></code> node.</li>
- <li>For each style or theme, add a <code><style></code> element with a unique <code>name</code> and, optionally, a <code>parent</code> attribute.
- The name is used for referencing these styles later, and the parent indicates what style resource to inherit from.</li>
- <li>Inside the <code><style></code> element, declare format values in one or more <code><item></code> element(s).
- Each <code><item></code> identifies its style property with a <code>name</code> attribute and defines its style value inside the element.</li>
- <li>You can then reference the custom resources from other XML resources, your manifest or application code.</li>
-</ol>
+<p>For example, by using a style, you can take this layout XML:</p>
+<pre>
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textColor="#00FF00"
+ android:typeface="monospace"
+ android:text="@string/hello" />
+</pre>
+<p>And turn it into this:</p>
+<pre>
+<TextView
+ style="@style/CodeFont"
+ android:text="@string/hello" />
+</pre>
+
+<p>All of the attributes related to style have been removed from the layout XML and put into a
+style definition called {@code CodeFont}, which is then applied with the <code>style</code>
+attribute. You'll see the definition for this style in the following section.</p>
+
+<p>A <strong>theme</strong> is a style applied to an entire {@link android.app.Activity} or
+application, rather than an individual {@link android.view.View} (as in the example above). When a
+style is applied as a theme, every View in the Activity or application will apply each style
+property that it supports. For example, you can apply the same {@code CodeFont} style
+as a theme for an Activity and then all text inside that Activity will have green monospace
+font.</p>
-<h2 id="styles">Styles</h2>
+<h2 id="DefiningStyles">Defining Styles</h2>
-<p>Here's an example declaration of a style: </p>
+<p>To create a set of styles, save an XML file in the {@code res/values/}
+directory of your project. The name of the XML file is arbitrary, but it must use the
+{@code .xml} extension and be saved in the {@code res/values/} folder.</p>
+
+<p>The root node of the XML file must be {@code <resources>}.</p>
+
+<p>For each style you want to create, add a {@code <style>} element to the file
+with a {@code name} that uniquely identifies the style (this attribute is required).
+Then add an {@code <item>} element for each property of that style, with a
+{@code name} that declares the style property and a value to go with it (this attribute
+is required). The value for the {@code <item>} can
+be a keyword string, a hex color, a reference to another resource type, or other value
+depending on the style property.
+Here's an example file with a single style:</p>
<pre>
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="SpecialText" parent="@style/Text">
- <item name="android:textSize">18sp</item>
- <item name="android:textColor">#008</item>
+ <style name="CodeFont" parent="@android:style/TextAppearance.Medium">
+ <item name="android:layout_width">fill_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textColor">#00FF00</item>
+ <item name="android:typeface">monospace</item>
</style>
</resources>
</pre>
-<p>As shown, you can use <code><item></code> elements to set specific formatting values for the style.
-The <code>name</code> attribute in the <code>item</code> can refer to a standard string, a hex color value,
-or a reference to any other resource type.</p>
+<p>Each child of the {@code <resources>} element is converted into an application resource
+object at compile-time, which can be referenced by the value in the {@code <style>} element's
+{@code name} attribute. This example style can be referenced from an XML layout as
+{@code @style/CodeFont} (as demonstrated in the introduction above).</p>
-<p>Notice the <code>parent</code> attribute in the <code><style></code> element. This attribute lets you specify a resource from which the current style will inherit values. The style can inherit from any type of resource that contains the style(s) you want. In general, your styles should always inherit (directly or indirectly) from a standard Android style resource. This way, you only have to define the values that you want to change.</p>
+<p>The <code>parent</code> attribute in the {@code <style>} element is optional and
+specifies the resource ID of another style from which this style should inherit
+properties. You can then override the inherited style properties if you want to.</p>
-<p>Here's how you would reference the custom style from an XML layout, in this case, for an EditText element:</p>
+<p>Remember, a style that you want to use as an Activity or application theme is defined in XML
+exactly the same as a style for a View. A style such as the one defined above can be applied as a
+style for a single View or as a theme for an entire Activity or application. How to apply a style
+for a single View or as an application theme is discussed later.</p>
+
+
+<h3 id="Inheritance">Inheritance</h3>
+
+<p>The {@code parent} attribute in the {@code <style>} element lets you specify a style
+from which your style should inherit properties.
+You can use this to inherit properties from an existing style and
+then define only the properties that you want to change or add. You can
+inherit from styles that you've created yourself or from styles that are built into the
+platform. (See <a href="#PlatformStyles">Using Platform Styles and Themes</a>, below, for
+information about inheriting from styles defined by the Android platform.) For example, you can
+inherit the Android platform's default text appearance and then modify it:</p>
<pre>
-<EditText id="@+id/text1"
- style="@style/SpecialText"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="Hello, World!" />
+ <style name="GreenText" parent="@android:style/TextAppearance">
+ <item name="android:textColor">#00FF00</item>
+ </style>
</pre>
-<p>Now this EditText widget will be styled as defined by the XML example above.</p>
-
-
-<h2 id="themes">Themes</h2>
-
-<p>Just like styles, themes are also declared in XML <code><style></code> elements, and are referenced in the same manner.
-The difference is that you add a theme to an entire application or activity, via the <code><application></code>
-and <code><activity></code> elements in the Android Manifest —
-themes cannot be applied to individual Views.</p>
-
-<p>Here's an example declaration of a theme:</p>
+<p>If you want to inherit from styles that you've defined yourself, you <em>do not</em> have to use
+the <code>parent</code> attribute. Instead, just prefix the name of the style you want to
+inherit to the name of your new style, separated by a period. For example, to create a new style
+that inherits the <code>CodeFont</code> style defined above, but make the color red,
+you can author the new style like this:</p>
<pre>
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <style name="CustomTheme">
- <item name="android:windowNoTitle">true</item>
- <item name="windowFrame">@drawable/screen_frame</item>
- <item name="windowBackground">@drawable/screen_background_white</item>
- <item name="panelForegroundColor">#FF000000</item>
- <item name="panelBackgroundColor">#FFFFFFFF</item>
- <item name="panelTextColor">?panelForegroundColor</item>
- <item name="panelTextSize">14</item>
- <item name="menuItemTextColor">?panelTextColor</item>
- <item name="menuItemTextSize">?panelTextSize</item>
- </style>
-</resources>
+ <style name="CodeFont.Red">
+ <item name="android:textColor">#FF0000</item>
+ </style>
</pre>
-<p>Notice the use of the at-symbol (@) and the question-mark (?) to reference resources.
-The at-symbol indicates that we're referencing a resource previously defined elsewhere (which may be from
-this project or from the Android framework).
-The question-mark indicates that we're referencing a resource value in the currently loaded theme. This
-is done by referring to a specific <code><item></code> by its <code>name</code> value. (E.g.,
-<em>panelTextColor</em> uses the same color assigned to <em>panelForegroundColor</em>, defined beforehand.)
-This technique can be used only in XML resources.
-</p>
+<p>Notice that there is no {@code parent} attribute in the {@code <style>} tag, but because
+the {@code name} attribute begins with the {@code CodeFont} style name (which
+is a style that you have created), this style inherits all style properties from that style. This
+style then overrides the {@code android:textColor} property to make the text red. You can
+reference this new style as {@code @style/CodeFont.Red}.</p>
-<h3 id="inTheManifest">Set the theme in the manifest</h3>
-<p>To set this theme for all the activities of your application, open the AndroidManifest.xml file and
+<p>You can continue inheriting like
+this as many times as you'd like, by chaining names with periods. For example, you can
+extend {@code CodeFont.Red} to be bigger, with:</p>
+<pre>
+ <style name="CodeFont.Red.Big">
+ <item name="android:textSize">30sp</item>
+ </style>
+</pre>
+<p>This inherits from both {@code CodeFont} and {@code CodeFont.Red} styles, then adds the
+{@code android:textSize} property.</p>
+
+<p class="note"><strong>Note:</strong> This technique for inheritance by chaining together
+names only works for styles defined by your own resources. You can't inherit Android built-in styles
+this way. To reference a built-in style, such as {@link android.R.style#TextAppearance}, you must
+use the {@code parent} attribute.</p>
+
+
+<h3 id="Properties">Style Properties</h3>
+
+<p>Now that you understand how a style is defined, you need to learn what kind
+of style properties—defined by the {@code <item>} element—are available.
+You're probably familiar with some already, such as {@link android.R.attr#layout_width} and
+{@link android.R.attr#textColor}. Of course, there are many more style properties you can use.</p>
+
+<p>The best place to find properties that apply to a specific {@link android.view.View} is the
+corresponding class reference, which lists all of the supported XML attributes. For example, all of the
+attributes listed in the table of
+<a href="{@docRoot}reference/android/widget/TextView.html#lattrs">TextView XML
+attributes</a> can be used in a style definition for a {@link android.widget.TextView} element (or one of
+its subclasses). One of the attributes listed in the reference is <a
+href="{@docRoot}reference/android/widget/TextView.html#attr_android:inputType">{@code
+android:inputType}</a>, so where you might normally place the <a
+href="{@docRoot}reference/android/widget/TextView.html#attr_android:inputType">{@code
+android:inputType}</a>
+attribute in an {@code <EditText>} element, like this:</p>
+<pre>
+<EditText
+ android:inputType="number"
+ ... />
+</pre>
+
+<p>You can instead create a style for the {@link android.widget.EditText} element that includes this property:</p>
+<pre>
+<style name="Numbers">
+ <item name="android:inputType">number</item>
+ ...
+</style>
+</pre>
+<p>So your XML for the layout can now implement this style:</p>
+<pre>
+<EditText
+ style="@style/Numbers"
+ ... />
+</pre>
+
+<p>This simple example may look like more work, but when you add more style properties and
+factor-in the ability to re-use the style in various places, the pay-off can be huge.</p>
+
+<p>For a reference of all available style properties, see the {@link android.R.attr}
+reference. Keep in mind that all View objects don't accept all the same style attributes, so you
+should normally refer to the specific {@link android.view.View} class for supported style
+properties. However, if you
+apply a style to a View that does not support all of the style properties, the View will
+apply only those properties that are supported and simply ignore the others.</p>
+
+<p>Some style properties, however, are not supported by any View element and can only be applied
+as a theme. These style properties apply to the entire window and not to any type of View.
+For example, style properties for a theme can hide the application title, hide the status bar,
+or change the window's background. These kind of style properties do not belong to any View object.
+To discover these theme-only style properties, look at the {@link android.R.attr} reference for
+attributes that begin with {@code window}. For instance, {@code windowNoTitle} and {@code
+windowBackground} are style properties that are effective only when the style is applied as
+a theme to an Activity or application. See the next section for information about applying a
+style as a theme.</p>
+
+<p class="note"><strong>Note:</strong> Don't forget to prefix the property names in each
+{@code <item>} element with the <code>android:</code> namespace. For example:
+{@code <item name="android:inputType">}.</p>
+
+
+
+<h2 id="ApplyingStyles">Applying Styles and Themes to the UI</h2>
+
+<p>There are two ways to set a style:</p>
+<ul>
+ <li>To an individual View, by adding the <code>style</code> attribute to a View
+ element in the XML for your layout.</li>
+ <li>Or, to an entire Activity or application, by adding the <code>android:theme</code>
+ attribute to the <code><activity></code> or <code><application></code> element
+ in the Android manifest.</li>
+</ul>
+
+<p>When you apply a style to a single {@link android.view.View} in the layout, the properties
+defined by the style are applied only to that {@link android.view.View}. If a style is applied to a
+{@link android.view.ViewGroup}, the child {@link android.view.View} elements will
+<strong>not</strong> inherit the style properties—only the element to which you directly apply
+the style will apply its properties. However, you <em>can</em> apply a style so that it
+applies to all {@link android.view.View} elements—by applying the style as a theme.</p>
+
+<p>To apply a style definition as a theme, you must apply the style to an
+{@link android.app.Activity} or application in the Android manifest. When you do so,
+every {@link android.view.View} within the Activity or
+application will apply each property that it supports. For example, if you apply the {@code
+CodeFont} style from the previous examples to an Activity, then all View elements
+that support the text style properties will apply them. Any View that does not support
+the properties will ignore them. If a View supports only some of the properties, then
+it will apply only those properties.</p>
+
+
+<h3 id="ApplyAStyle">Apply a style to a View</h3>
+
+<p>Here's how to set a style for a View in the XML layout:</p>
+
+<pre>
+<TextView
+ style="@style/CodeFont"
+ android:text="@string/hello" />
+</pre>
+
+<p>Now this TextView will be styled as defined by the style named {@code CodeFont}.
+(See the sample above, in <a href="#DefiningStyles">Defining Styles</a>.)</p>
+
+<p class="note"><strong>Note:</strong> The <code>style</code> attribute
+does <em>not</em> use the <code>android:</code> namespace prefix.</p>
+
+
+<h3 id="ApplyATheme">Apply a theme to an Activity or application</h3>
+
+<p>To set a theme for all the activities of your application, open the {@code AndroidManifest.xml} file and
edit the <code><application></code> tag to include the <code>android:theme</code> attribute with the
-theme name:</p>
+style name. For example:</p>
<pre>
<application android:theme="@style/CustomTheme">
</pre>
-<p>If you want the theme applied to just one Activity in your application, then add the theme
-attribute to the <code><activity></code> tag, instead.</p>
+<p>If you want a theme applied to just one Activity in your application, then add the
+<code>android:theme</code> attribute to the <code><activity></code> tag instead.</p>
-<p>Just as Android provides other built-in resources, there are several themes that you swap in
-without having to write one yourself. For example, you can use the Dialog theme to make your Activity
-appear like a dialog box. In the manifest, reference an Android theme like so:</p>
+<p>Just as Android provides other built-in resources, there are many pre-defined themes that you can use, to avoid
+writing them yourself. For example, you can use the {@code Dialog} theme and make your Activity
+appear like a dialog box:</p>
<pre>
<activity android:theme="@android:style/Theme.Dialog">
</pre>
-<p>If you like a theme, but want to slightly tweak it, just add the theme as the <code>parent</code> of your custom theme.
-For example, we'll modify the <code>Theme.Dialog</code> theme. To do so, create a style
-with <code>Theme.Dialog</code> as the parent:</p>
+<p>Or if you want the background to be transparent, use the Translucent theme:</p>
+
+<pre>
+<activity android:theme="@android:style/Theme.Translucent">
+</pre>
+
+<p>If you like a theme, but want to tweak it, just add the theme as the <code>parent</code>
+of your custom theme. For example, you can modify the traditional dialog theme to use your own
+background image like this:</p>
<pre>
<style name="CustomDialogTheme" parent="@android:style/Theme.Dialog">
+ <item name="android:windowBackground">@drawable/custom_dialog_background</item>
+</style>
</pre>
-<p>There it is. We've inherited the Android Dialog theme so we can adjust its styles as we like.
-So, for each item in the Dialog theme that we want to change, we re-define the value here and
-use <var>CustomDialogTheme</var> instead of <var>Theme.Dialog</var> inside the Android Manifest.</p>
-<h3 id="fromTheApp">Set the theme from the application</h3>
-<p>You can also load a theme for an Activity programmatically, if needed.
-To do so, use the {@link android.app.Activity#setTheme(int) setTheme()}
-method. Note that, when doing so, you must be sure to set the theme <em>before</em>
+<p>Now use {@code CustomDialogTheme} instead of {@code Theme.Dialog} inside the Android
+Manifest:</p>
+
+<pre>
+<activity android:theme="@style/CustomDialogTheme">
+</pre>
+
+
+<!-- This currently has some bugs
+
+<h3 id="setThemeFromTheApp">Set the theme from the application</h3>
+
+<p>We recommend that you set your themes in you Android manifest, as described above, because it's simple and
+keeps your program code focused on application functionality, rather than style. But if it's necessary
+for you to change your theme programatically (perhaps based on a user preference), you can.</p>
+
+<p>To set the theme in your program code, use the {@link android.content.ContextWrapper#setTheme(int)}
+method and pass it the theme resource ID. Note that, when doing so, you must be sure to set the theme <em>before</em>
instantiating any Views in the context, for example, before calling
<code>setContentView(View)</code> or <code>inflate(int, ViewGroup)</code>. This ensures that
the system applies the same theme for all of your UI screens. Here's an example:</p>
@@ -164,12 +349,40 @@
you want to apply a theme to your main screen, doing so in XML
is a better approach. </p>
-<p>For detailed information about custom styles and themes and referencing them from your application, see
+-->
+
+
+
+<h2 id="PlatformStyles">Using Platform Styles and Themes</h2>
+
+<p>The Android platform provides a large collection of styles and themes that you can
+use in your applications. You can find a reference of all available styles in the
+{@link android.R.style} class. To use the styles listed here, replace all underscores in
+the style name with a period. For example, you can apply the
+{@link android.R.style#Theme_NoTitleBar} theme with
+{@code "@android:style/Theme.NoTitleBar"}.</p>
+
+<p>The {@link android.R.style} reference, however, is not well documented and does not
+thoroughly describe the styles, so viewing the actual source code for these styles and
+themes will give you a better understanding of what style properties each one provides.
+For a better reference to the Android styles and themes, see the following source code:</p>
+<ul>
+ <li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/res/res/values/styles.xml;h=d7b654e49809cb97a35682754b1394af5c8bc88b;hb=HEAD">Android Styles (styles.xml)</a></li>
+ <li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/res/res/values/themes.xml;h=6b3d7407d1c895a3c297e60d5beac98e2d34c271;hb=HEAD">Android Themes (themes.xml)</a></li>
+</ul>
+
+<p>These files will help you learn through example. For instance, in the Android themes source code,
+you'll find a declaration for <code><style name="Theme.Dialog"></code>. In this definition,
+you'll see all of the properties that are used to style dialogs that are used by the Android
+framework.</p>
+
+<p>For more information about the syntax used to create styles in XML, see
<a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Available Resource Types:
Style and Themes</a>.</p>
-<p>For information about default themes and styles available, see {@link android.R.style}.</p>
-
+<p>For a reference of available style attributes that you can use to define a style or theme
+(e.g., "windowBackground" or "textAppearance"), see {@link android.R.attr} or the respective
+View class for which you are creating a style.</p>
diff --git a/docs/html/resources/samples/images/SampleSyncAdapter1.png b/docs/html/resources/samples/images/SampleSyncAdapter1.png
new file mode 100644
index 0000000..242fd36
--- /dev/null
+++ b/docs/html/resources/samples/images/SampleSyncAdapter1.png
Binary files differ
diff --git a/docs/html/resources/samples/images/SampleSyncAdapter2.png b/docs/html/resources/samples/images/SampleSyncAdapter2.png
new file mode 100644
index 0000000..055beaf
--- /dev/null
+++ b/docs/html/resources/samples/images/SampleSyncAdapter2.png
Binary files differ
diff --git a/docs/html/resources/samples/images/SampleSyncAdapter3.png b/docs/html/resources/samples/images/SampleSyncAdapter3.png
new file mode 100644
index 0000000..8ec468e
--- /dev/null
+++ b/docs/html/resources/samples/images/SampleSyncAdapter3.png
Binary files differ
diff --git a/include/media/MediaProfiles.h b/include/media/MediaProfiles.h
index be928ec..3f253f9 100644
--- a/include/media/MediaProfiles.h
+++ b/include/media/MediaProfiles.h
@@ -53,8 +53,8 @@
*
* Supported param name are:
* file.format - output file format. see mediarecorder.h for details
- * codec.vid - video encoder. see mediarecorder.h for details.
- * codec.aud - audio encoder. see mediarecorder.h for details.
+ * vid.codec - video encoder. see mediarecorder.h for details.
+ * aud.codec - audio encoder. see mediarecorder.h for details.
* vid.width - video frame width
* vid.height - video frame height
* vid.fps - video frame rate
diff --git a/include/media/mediametadataretriever.h b/include/media/mediametadataretriever.h
index 113c452..022b849 100644
--- a/include/media/mediametadataretriever.h
+++ b/include/media/mediametadataretriever.h
@@ -54,6 +54,7 @@
METADATA_KEY_VIDEO_WIDTH = 20,
METADATA_KEY_WRITER = 21,
METADATA_KEY_MIMETYPE = 22,
+ METADATA_KEY_DISC_NUMBER = 23,
// Add more here...
};
diff --git a/include/media/stagefright/AudioPlayer.h b/include/media/stagefright/AudioPlayer.h
index 26fcc95..843e051 100644
--- a/include/media/stagefright/AudioPlayer.h
+++ b/include/media/stagefright/AudioPlayer.h
@@ -41,9 +41,6 @@
// Caller retains ownership of "source".
void setSource(const sp<MediaSource> &source);
- void setListenerCallback(
- void (*notify)(void *cookie, int what), void *cookie);
-
// Return time in us.
virtual int64_t getRealTimeUs();
@@ -63,6 +60,9 @@
status_t seekTo(int64_t time_us);
+ bool isSeeking();
+ bool reachedEOS();
+
private:
sp<MediaSource> mSource;
AudioTrack *mAudioTrack;
@@ -80,13 +80,11 @@
int64_t mPositionTimeRealUs;
bool mSeeking;
+ bool mReachedEOS;
int64_t mSeekTimeUs;
bool mStarted;
- void (*mListenerCallback)(void *cookie, int what);
- void *mListenerCookie;
-
sp<MediaPlayerBase::AudioSink> mAudioSink;
static void AudioCallback(int event, void *user, void *info);
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 3b21468..8f423f7 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -59,6 +59,7 @@
kKeyAlbumArtMIME = 'alAM', // cstring
kKeyAuthor = 'auth', // cstring
kKeyCDTrackNumber = 'cdtr', // cstring
+ kKeyDiscNumber = 'dnum', // cstring
kKeyDate = 'date', // cstring
kKeyWriter = 'writ', // cstring
};
diff --git a/include/private/opengles/gl_context.h b/include/private/opengles/gl_context.h
index e0deba9..c7db9a6 100644
--- a/include/private/opengles/gl_context.h
+++ b/include/private/opengles/gl_context.h
@@ -294,7 +294,6 @@
vec4_t normalizedObjPosition;
vec4_t spotDir;
vec4_t normalizedSpotDir;
- vec4_t objViewer;
GLfixed spotExp;
GLfixed spotCutoff;
GLfixed spotCutoffCosine;
@@ -327,9 +326,10 @@
material_t front;
light_model_t lightModel;
color_material_t colorMaterial;
+ vec4_t implicitSceneEmissionAndAmbient;
+ vec4_t objViewer;
uint32_t enabledLights;
GLboolean enable;
- vec4_t implicitSceneEmissionAndAmbient;
GLenum shadeModel;
typedef void (*light_fct_t)(ogles_context_t*, vertex_t*);
void (*lightVertex)(ogles_context_t* c, vertex_t* v);
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 0a2fe4c..6041b83 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -28,16 +28,16 @@
* {@hide}
*/
public class KeyStore {
- public static int NO_ERROR = 1;
- public static int LOCKED = 2;
- public static int UNINITIALIZED = 3;
- public static int SYSTEM_ERROR = 4;
- public static int PROTOCOL_ERROR = 5;
- public static int PERMISSION_DENIED = 6;
- public static int KEY_NOT_FOUND = 7;
- public static int VALUE_CORRUPTED = 8;
- public static int UNDEFINED_ACTION = 9;
- public static int WRONG_PASSWORD = 10;
+ public static final int NO_ERROR = 1;
+ public static final int LOCKED = 2;
+ public static final int UNINITIALIZED = 3;
+ public static final int SYSTEM_ERROR = 4;
+ public static final int PROTOCOL_ERROR = 5;
+ public static final int PERMISSION_DENIED = 6;
+ public static final int KEY_NOT_FOUND = 7;
+ public static final int VALUE_CORRUPTED = 8;
+ public static final int UNDEFINED_ACTION = 9;
+ public static final int WRONG_PASSWORD = 10;
private static final LocalSocketAddress sAddress = new LocalSocketAddress(
"keystore", LocalSocketAddress.Namespace.RESERVED);
@@ -56,8 +56,8 @@
}
public byte[] get(byte[] key) {
- byte[][] values = execute('g', key);
- return (values == null) ? null : values[0];
+ ArrayList<byte[]> values = execute('g', key);
+ return (values == null || values.size() == 0) ? null : values.get(0);
}
public String get(String key) {
@@ -93,7 +93,8 @@
}
public byte[][] saw(byte[] prefix) {
- return execute('s', prefix);
+ ArrayList<byte[]> values = execute('s', prefix);
+ return (values == null) ? null : values.toArray(new byte[values.size()][]);
}
public String[] saw(String prefix) {
@@ -148,7 +149,7 @@
return mError;
}
- private byte[][] execute(int code, byte[]... parameters) {
+ private ArrayList<byte[]> execute(int code, byte[]... parameters) {
mError = PROTOCOL_ERROR;
for (byte[] parameter : parameters) {
@@ -179,7 +180,7 @@
return null;
}
- ArrayList<byte[]> results = new ArrayList<byte[]>();
+ ArrayList<byte[]> values = new ArrayList<byte[]>();
while (true) {
int i, j;
if ((i = in.read()) == -1) {
@@ -188,16 +189,16 @@
if ((j = in.read()) == -1) {
return null;
}
- byte[] result = new byte[i << 8 | j];
- for (i = 0; i < result.length; i += j) {
- if ((j = in.read(result, i, result.length - i)) == -1) {
+ byte[] value = new byte[i << 8 | j];
+ for (i = 0; i < value.length; i += j) {
+ if ((j = in.read(value, i, value.length - i)) == -1) {
return null;
}
}
- results.add(result);
+ values.add(value);
}
mError = NO_ERROR;
- return results.toArray(new byte[results.size()][]);
+ return values;
} catch (IOException e) {
// ignore
} finally {
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 88521f6..98464a0 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -110,5 +110,9 @@
include $(BUILD_SHARED_LIBRARY)
+# include the java examples
+include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk,\
+ java \
+ ))
endif #simulator
diff --git a/libs/rs/rsVertexArray.cpp b/libs/rs/rsVertexArray.cpp
index d0c0414..6c2002d 100644
--- a/libs/rs/rsVertexArray.cpp
+++ b/libs/rs/rsVertexArray.cpp
@@ -191,6 +191,9 @@
if (sc->isUserVertexProgram()) {
slot = sc->vtxAttribSlot(ct);
} else {
+ if (mAttribs[ct].kind == RS_KIND_USER) {
+ continue;
+ }
slot = sc->vtxAttribSlot(mAttribs[ct].kind);
}
diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp
index 2735aa2..bd3113b 100644
--- a/libs/surfaceflinger/LayerBuffer.cpp
+++ b/libs/surfaceflinger/LayerBuffer.cpp
@@ -328,7 +328,8 @@
LayerBuffer::BufferSource::BufferSource(LayerBuffer& layer,
const ISurface::BufferHeap& buffers)
- : Source(layer), mStatus(NO_ERROR), mBufferSize(0)
+ : Source(layer), mStatus(NO_ERROR), mBufferSize(0),
+ mUseEGLImageDirectly(true)
{
if (buffers.heap == NULL) {
// this is allowed, but in this case, it is illegal to receive
@@ -466,25 +467,38 @@
#if defined(EGL_ANDROID_image_native_buffer)
if (mLayer.mFlags & DisplayHardware::DIRECT_TEXTURE) {
- copybit_device_t* copybit = mLayer.mBlitEngine;
- if (copybit && ourBuffer->supportsCopybit()) {
- // create our EGLImageKHR the first time
- err = initTempBuffer();
- if (err == NO_ERROR) {
+ err = INVALID_OPERATION;
+ if (ourBuffer->supportsCopybit()) {
+ // First, try to use the buffer as an EGLImage directly
+ if (mUseEGLImageDirectly) {
// NOTE: Assume the buffer is allocated with the proper USAGE flags
- const NativeBuffer& dst(mTempBuffer);
- region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b)));
- copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0);
- copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF);
- copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
- err = copybit->stretch(copybit, &dst.img, &src.img,
- &dst.crop, &src.crop, &clip);
+ sp<GraphicBuffer> buffer = new GraphicBuffer(
+ src.img.w, src.img.h, src.img.format,
+ GraphicBuffer::USAGE_HW_TEXTURE,
+ src.img.w, src.img.handle, false);
+ err = mLayer.initializeEglImage(buffer, &mTexture);
if (err != NO_ERROR) {
- clearTempBufferImage();
+ mUseEGLImageDirectly = false;
}
}
- } else {
- err = INVALID_OPERATION;
+ copybit_device_t* copybit = mLayer.mBlitEngine;
+ if (copybit && err != NO_ERROR) {
+ // create our EGLImageKHR the first time
+ err = initTempBuffer();
+ if (err == NO_ERROR) {
+ // NOTE: Assume the buffer is allocated with the proper USAGE flags
+ const NativeBuffer& dst(mTempBuffer);
+ region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b)));
+ copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0);
+ copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF);
+ copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
+ err = copybit->stretch(copybit, &dst.img, &src.img,
+ &dst.crop, &src.crop, &clip);
+ if (err != NO_ERROR) {
+ clearTempBufferImage();
+ }
+ }
+ }
}
}
#endif
diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h
index e03f92c..3257b76 100644
--- a/libs/surfaceflinger/LayerBuffer.h
+++ b/libs/surfaceflinger/LayerBuffer.h
@@ -145,6 +145,7 @@
mutable LayerBase::Texture mTexture;
mutable NativeBuffer mTempBuffer;
mutable sp<GraphicBuffer> mTempGraphicBuffer;
+ mutable bool mUseEGLImageDirectly;
};
class OverlaySource : public Source {
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 1fac07c..628cb6b7 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -81,10 +81,6 @@
void setTestProviderStatus(String provider, int status, in Bundle extras, long updateTime);
void clearTestProviderStatus(String provider);
- /* for installing external Location Providers */
- void installLocationProvider(String name, ILocationProvider provider);
- void installGeocodeProvider(IGeocodeProvider provider);
-
// for NI support
boolean sendNiResponse(int notifId, int userResponse);
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 6d7a23d..9027fc2 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -104,48 +104,6 @@
*/
public static final String KEY_LOCATION_CHANGED = "location";
- public interface GeocodeProvider {
- String getFromLocation(double latitude, double longitude, int maxResults,
- GeocoderParams params, List<Address> addrs);
-
- String getFromLocationName(String locationName,
- double lowerLeftLatitude, double lowerLeftLongitude,
- double upperRightLatitude, double upperRightLongitude, int maxResults,
- GeocoderParams params, List<Address> addrs);
- }
-
- private static final class GeocodeProviderProxy extends IGeocodeProvider.Stub {
- private GeocodeProvider mProvider;
-
- GeocodeProviderProxy(GeocodeProvider provider) {
- mProvider = provider;
- }
-
- /**
- * This method is overridden to implement the
- * {@link Geocoder#getFromLocation(double, double, int)} method.
- * Classes implementing this method should not hold a reference to the params parameter.
- */
- public String getFromLocation(double latitude, double longitude, int maxResults,
- GeocoderParams params, List<Address> addrs) {
- return mProvider.getFromLocation(latitude, longitude, maxResults, params, addrs);
- }
-
- /**
- * This method is overridden to implement the
- * {@link Geocoder#getFromLocationName(String, int, double, double, double, double)} method.
- * Classes implementing this method should not hold a reference to the params parameter.
- */
- public String getFromLocationName(String locationName,
- double lowerLeftLatitude, double lowerLeftLongitude,
- double upperRightLatitude, double upperRightLongitude, int maxResults,
- GeocoderParams params, List<Address> addrs) {
- return mProvider.getFromLocationName(locationName, lowerLeftLatitude,
- lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
- maxResults, params, addrs);
- }
- }
-
// Map from LocationListeners to their associated ListenerTransport objects
private HashMap<LocationListener,ListenerTransport> mListeners =
new HashMap<LocationListener,ListenerTransport>();
@@ -1395,75 +1353,6 @@
return false;
}
}
-
- /**
- * Installs a location provider.
- *
- * @param name of the location provider
- * @param provider Binder interface for the location provider
- *
- * @return true if the command succeeds.
- *
- * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission.
- *
- * {@hide}
- */
- public boolean installLocationProvider(String name, ILocationProvider provider) {
- try {
- mService.installLocationProvider(name, provider);
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in installLocationProvider: ", e);
- return false;
- }
- }
-
- /**
- * Installs a location provider.
- *
- * @param provider implementation of the location provider
- *
- * @return true if the command succeeds.
- *
- * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission.
- */
- public boolean installLocationProvider(LocationProviderImpl provider) {
- return installLocationProvider(provider.getName(), provider.getInterface());
- }
-
- /**
- * Installs a geocoder server.
- *
- * @param provider Binder interface for the geocoder provider
- *
- * @return true if the command succeeds.
- *
- * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission.
- */
- public boolean installGeocodeProvider(GeocodeProvider provider) {
- try {
- mService.installGeocodeProvider(new GeocodeProviderProxy(provider));
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in setGeocodeProvider: ", e);
- return false;
- }
- }
-
- /**
- * Used by location providers to report new locations.
- *
- * @param location new Location to report
- *
- * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission.
- */
- public void reportLocation(Location location) {
- try {
- mService.reportLocation(location);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in reportLocation: ", e);
- }
- }
/**
* Used by NetInitiatedActivity to report user response
diff --git a/location/java/android/location/LocationProviderImpl.java b/location/java/android/location/LocationProviderImpl.java
deleted file mode 100644
index 7148a02..0000000
--- a/location/java/android/location/LocationProviderImpl.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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.location;
-
-import android.net.NetworkInfo;
-import android.os.Bundle;
-
-/**
- * An abstract superclass for location providers that are implemented
- * outside of the core android platform.
- * A LocationProviderImpl can be installed using the
- * {@link LocationManager#installLocationProvider(LocationProviderImpl)} method.
- * Installing a location provider requires the
- * android.permission.INSTALL_LOCATION_PROVIDER permission.
- */
-public abstract class LocationProviderImpl extends LocationProvider {
-
- private ILocationProvider.Stub mProvider = new ILocationProvider.Stub() {
-
- public boolean requiresNetwork() {
- return LocationProviderImpl.this.requiresNetwork();
- }
-
- public boolean requiresSatellite() {
- return LocationProviderImpl.this.requiresSatellite();
- }
-
- public boolean requiresCell() {
- return LocationProviderImpl.this.requiresCell();
- }
-
- public boolean hasMonetaryCost() {
- return LocationProviderImpl.this.hasMonetaryCost();
- }
-
- public boolean supportsAltitude() {
- return LocationProviderImpl.this.supportsAltitude();
- }
-
- public boolean supportsSpeed() {
- return LocationProviderImpl.this.supportsSpeed();
- }
-
- public boolean supportsBearing() {
- return LocationProviderImpl.this.supportsBearing();
- }
-
- public int getPowerRequirement() {
- return LocationProviderImpl.this.getPowerRequirement();
- }
-
- public int getAccuracy() {
- return LocationProviderImpl.this.getAccuracy();
- }
-
- public void enable() {
- LocationProviderImpl.this.enable();
- }
-
- public void disable() {
- LocationProviderImpl.this.disable();
- }
-
- public int getStatus(Bundle extras) {
- return LocationProviderImpl.this.getStatus(extras);
- }
-
- public long getStatusUpdateTime() {
- return LocationProviderImpl.this.getStatusUpdateTime();
- }
-
- public void enableLocationTracking(boolean enable) {
- LocationProviderImpl.this.enableLocationTracking(enable);
- }
-
- public void setMinTime(long minTime) {
- LocationProviderImpl.this.setMinTime(minTime);
- }
-
- public void updateNetworkState(int state, NetworkInfo info) {
- LocationProviderImpl.this.updateNetworkState(state, info);
- }
-
- public void updateLocation(Location location) {
- LocationProviderImpl.this.updateLocation(location);
- }
-
- public boolean sendExtraCommand(String command, Bundle extras) {
- return LocationProviderImpl.this.sendExtraCommand(command, extras);
- }
-
- public void addListener(int uid) {
- LocationProviderImpl.this.addListener(uid);
- }
-
- public void removeListener(int uid) {
- LocationProviderImpl.this.removeListener(uid);
- }
- };
-
- public LocationProviderImpl(String name) {
- super(name);
- }
-
- /**
- * {@hide}
- */
- /* package */ ILocationProvider getInterface() {
- return mProvider;
- }
-
- /**
- * Enables the location provider
- */
- public abstract void enable();
-
- /**
- * Disables the location provider
- */
- public abstract void disable();
-
- /**
- * Returns a information on the status of this provider.
- * {@link #OUT_OF_SERVICE} is returned if the provider is
- * out of service, and this is not expected to change in the near
- * future; {@link #TEMPORARILY_UNAVAILABLE} is returned if
- * the provider is temporarily unavailable but is expected to be
- * available shortly; and {@link #AVAILABLE} is returned
- * if the provider is currently available.
- *
- * <p> If extras is non-null, additional status information may be
- * added to it in the form of provider-specific key/value pairs.
- */
- public abstract int getStatus(Bundle extras);
-
- /**
- * Returns the time at which the status was last updated. It is the
- * responsibility of the provider to appropriately set this value using
- * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
- * there is a status update that it wishes to broadcast to all its
- * listeners. The provider should be careful not to broadcast
- * the same status again.
- *
- * @return time of last status update in millis since last reboot
- */
- public abstract long getStatusUpdateTime();
-
- /**
- * Notifies the location provider that clients are listening for locations.
- * Called with enable set to true when the first client is added and
- * called with enable set to false when the last client is removed.
- * This allows the provider to prepare for receiving locations,
- * and to shut down when no clients are remaining.
- *
- * @param enable true if location tracking should be enabled.
- */
- public abstract void enableLocationTracking(boolean enable);
-
- /**
- * Notifies the location provider of the smallest minimum time between updates amongst
- * all clients that are listening for locations. This allows the provider to reduce
- * the frequency of updates to match the requested frequency.
- *
- * @param minTime the smallest minTime value over all listeners for this provider.
- */
- public abstract void setMinTime(long minTime);
-
- /**
- * Updates the network state for the given provider. This function must
- * be overwritten if {@link #requiresNetwork} returns true. The state is
- * {@link #TEMPORARILY_UNAVAILABLE} (disconnected), OR {@link #AVAILABLE}
- * (connected or connecting).
- *
- * @param state data state
- */
- public abstract void updateNetworkState(int state, NetworkInfo info);
-
- /**
- * Informs the provider when a new location has been computed by a different
- * location provider. This is intended to be used as aiding data for the
- * receiving provider.
- *
- * @param location new location from other location provider
- */
- public abstract void updateLocation(Location location);
-
- /**
- * Implements addditional location provider specific additional commands.
- *
- * @param command name of the command to send to the provider.
- * @param extras optional arguments for the command (or null).
- * The provider may optionally fill the extras Bundle with results from the command.
- *
- * @return true if the command succeeds.
- */
- public abstract boolean sendExtraCommand(String command, Bundle extras);
-
- /**
- * Notifies the location provider when a new client is listening for locations.
- *
- * @param uid user ID of the new client.
- */
- public abstract void addListener(int uid);
-
- /**
- * Notifies the location provider when a client is no longer listening for locations.
- *
- * @param uid user ID of the client no longer listening.
- */
- public abstract void removeListener(int uid);
-}
-
diff --git a/location/java/android/location/provider/GeocodeProvider.java b/location/java/android/location/provider/GeocodeProvider.java
new file mode 100644
index 0000000..86376a7
--- /dev/null
+++ b/location/java/android/location/provider/GeocodeProvider.java
@@ -0,0 +1,83 @@
+/*
+ * 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.location.provider;
+
+import android.os.IBinder;
+
+import android.location.Address;
+import android.location.GeocoderParams;
+import android.location.IGeocodeProvider;
+
+import java.util.List;
+
+/**
+ * An abstract superclass for geocode providers that are implemented
+ * outside of the core android platform.
+ * Geocode providers can be implemented as services and return the result of
+ * {@link GeocodeProvider#getBinder()} in its getBinder() method.
+ *
+ * @hide
+ */
+public abstract class GeocodeProvider {
+
+ private IGeocodeProvider.Stub mProvider = new IGeocodeProvider.Stub() {
+ public String getFromLocation(double latitude, double longitude, int maxResults,
+ GeocoderParams params, List<Address> addrs) {
+ return GeocodeProvider.this.onGetFromLocation(latitude, longitude, maxResults,
+ params, addrs);
+ }
+
+ public String getFromLocationName(String locationName,
+ double lowerLeftLatitude, double lowerLeftLongitude,
+ double upperRightLatitude, double upperRightLongitude, int maxResults,
+ GeocoderParams params, List<Address> addrs) {
+ return GeocodeProvider.this.onGetFromLocationName(locationName, lowerLeftLatitude,
+ lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+ maxResults, params, addrs);
+ }
+ };
+
+ /**
+ * This method is overridden to implement the
+ * {@link Geocoder#getFromLocation(double, double, int)} method.
+ * Classes implementing this method should not hold a reference to the params parameter.
+ */
+ public abstract String onGetFromLocation(double latitude, double longitude, int maxResults,
+ GeocoderParams params, List<Address> addrs);
+
+ /**
+ * This method is overridden to implement the
+ * {@link Geocoder#getFromLocationName(String, int, double, double, double, double)} method.
+ * Classes implementing this method should not hold a reference to the params parameter.
+ */
+ public abstract String onGetFromLocationName(String locationName,
+ double lowerLeftLatitude, double lowerLeftLongitude,
+ double upperRightLatitude, double upperRightLongitude, int maxResults,
+ GeocoderParams params, List<Address> addrs);
+
+ /**
+ * Returns the Binder interface for the geocode provider.
+ * This is intended to be used for the onBind() method of
+ * a service that implements a geocoder service.
+ *
+ * @return the IBinder instance for the provider
+ */
+ public IBinder getBinder() {
+ return mProvider;
+ }
+}
+
diff --git a/location/java/android/location/provider/LocationProvider.java b/location/java/android/location/provider/LocationProvider.java
new file mode 100644
index 0000000..0d028c0
--- /dev/null
+++ b/location/java/android/location/provider/LocationProvider.java
@@ -0,0 +1,333 @@
+/*
+ * 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.location.provider;
+
+import android.content.Context;
+import android.net.NetworkInfo;
+import android.location.ILocationManager;
+import android.location.ILocationProvider;
+import android.location.Location;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * An abstract superclass for location providers that are implemented
+ * outside of the core android platform.
+ * Location providers can be implemented as services and return the result of
+ * {@link LocationProvider#getBinder()} in its getBinder() method.
+ *
+ * @hide
+ */
+public abstract class LocationProvider {
+
+ private static final String TAG = "LocationProvider";
+
+ private ILocationManager mLocationManager;
+
+ private ILocationProvider.Stub mProvider = new ILocationProvider.Stub() {
+
+ public boolean requiresNetwork() {
+ return LocationProvider.this.onRequiresNetwork();
+ }
+
+ public boolean requiresSatellite() {
+ return LocationProvider.this.onRequiresSatellite();
+ }
+
+ public boolean requiresCell() {
+ return LocationProvider.this.onRequiresCell();
+ }
+
+ public boolean hasMonetaryCost() {
+ return LocationProvider.this.onHasMonetaryCost();
+ }
+
+ public boolean supportsAltitude() {
+ return LocationProvider.this.onSupportsAltitude();
+ }
+
+ public boolean supportsSpeed() {
+ return LocationProvider.this.onSupportsSpeed();
+ }
+
+ public boolean supportsBearing() {
+ return LocationProvider.this.onSupportsBearing();
+ }
+
+ public int getPowerRequirement() {
+ return LocationProvider.this.onGetPowerRequirement();
+ }
+
+ public int getAccuracy() {
+ return LocationProvider.this.onGetAccuracy();
+ }
+
+ public void enable() {
+ LocationProvider.this.onEnable();
+ }
+
+ public void disable() {
+ LocationProvider.this.onDisable();
+ }
+
+ public int getStatus(Bundle extras) {
+ return LocationProvider.this.onGetStatus(extras);
+ }
+
+ public long getStatusUpdateTime() {
+ return LocationProvider.this.onGetStatusUpdateTime();
+ }
+
+ public void enableLocationTracking(boolean enable) {
+ LocationProvider.this.onEnableLocationTracking(enable);
+ }
+
+ public void setMinTime(long minTime) {
+ LocationProvider.this.onSetMinTime(minTime);
+ }
+
+ public void updateNetworkState(int state, NetworkInfo info) {
+ LocationProvider.this.onUpdateNetworkState(state, info);
+ }
+
+ public void updateLocation(Location location) {
+ LocationProvider.this.onUpdateLocation(location);
+ }
+
+ public boolean sendExtraCommand(String command, Bundle extras) {
+ return LocationProvider.this.onSendExtraCommand(command, extras);
+ }
+
+ public void addListener(int uid) {
+ LocationProvider.this.onAddListener(uid);
+ }
+
+ public void removeListener(int uid) {
+ LocationProvider.this.onRemoveListener(uid);
+ }
+ };
+
+ public LocationProvider() {
+ IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE);
+ mLocationManager = ILocationManager.Stub.asInterface(b);
+ }
+
+ /**
+ * {@hide}
+ */
+ /* package */ ILocationProvider getInterface() {
+ return mProvider;
+ }
+
+ /**
+ * Returns the Binder interface for the location provider.
+ * This is intended to be used for the onBind() method of
+ * a service that implements a location provider service.
+ *
+ * @return the IBinder instance for the provider
+ */
+ public IBinder getBinder() {
+ return mProvider;
+ }
+
+ /**
+ * Used by the location provider to report new locations.
+ *
+ * @param location new Location to report
+ *
+ * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission.
+ */
+ public void reportLocation(Location location) {
+ try {
+ mLocationManager.reportLocation(location);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in reportLocation: ", e);
+ }
+ }
+
+ /**
+ * Returns true if the provider requires access to a
+ * data network (e.g., the Internet), false otherwise.
+ */
+ public abstract boolean onRequiresNetwork();
+
+ /**
+ * Returns true if the provider requires access to a
+ * satellite-based positioning system (e.g., GPS), false
+ * otherwise.
+ */
+ public abstract boolean onRequiresSatellite();
+
+ /**
+ * Returns true if the provider requires access to an appropriate
+ * cellular network (e.g., to make use of cell tower IDs), false
+ * otherwise.
+ */
+ public abstract boolean onRequiresCell();
+
+ /**
+ * Returns true if the use of this provider may result in a
+ * monetary charge to the user, false if use is free. It is up to
+ * each provider to give accurate information.
+ */
+ public abstract boolean onHasMonetaryCost();
+
+ /**
+ * Returns true if the provider is able to provide altitude
+ * information, false otherwise. A provider that reports altitude
+ * under most circumstances but may occassionally not report it
+ * should return true.
+ */
+ public abstract boolean onSupportsAltitude();
+
+ /**
+ * Returns true if the provider is able to provide speed
+ * information, false otherwise. A provider that reports speed
+ * under most circumstances but may occassionally not report it
+ * should return true.
+ */
+ public abstract boolean onSupportsSpeed();
+
+ /**
+ * Returns true if the provider is able to provide bearing
+ * information, false otherwise. A provider that reports bearing
+ * under most circumstances but may occassionally not report it
+ * should return true.
+ */
+ public abstract boolean onSupportsBearing();
+
+ /**
+ * Returns the power requirement for this provider.
+ *
+ * @return the power requirement for this provider, as one of the
+ * constants Criteria.POWER_REQUIREMENT_*.
+ */
+ public abstract int onGetPowerRequirement();
+
+ /**
+ * Returns a constant describing horizontal accuracy of this provider.
+ * If the provider returns finer grain or exact location,
+ * {@link Criteria#ACCURACY_FINE} is returned, otherwise if the
+ * location is only approximate then {@link Criteria#ACCURACY_COARSE}
+ * is returned.
+ */
+ public abstract int onGetAccuracy();
+
+ /**
+ * Enables the location provider
+ */
+ public abstract void onEnable();
+
+ /**
+ * Disables the location provider
+ */
+ public abstract void onDisable();
+
+ /**
+ * Returns a information on the status of this provider.
+ * {@link #OUT_OF_SERVICE} is returned if the provider is
+ * out of service, and this is not expected to change in the near
+ * future; {@link #TEMPORARILY_UNAVAILABLE} is returned if
+ * the provider is temporarily unavailable but is expected to be
+ * available shortly; and {@link #AVAILABLE} is returned
+ * if the provider is currently available.
+ *
+ * <p> If extras is non-null, additional status information may be
+ * added to it in the form of provider-specific key/value pairs.
+ */
+ public abstract int onGetStatus(Bundle extras);
+
+ /**
+ * Returns the time at which the status was last updated. It is the
+ * responsibility of the provider to appropriately set this value using
+ * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
+ * there is a status update that it wishes to broadcast to all its
+ * listeners. The provider should be careful not to broadcast
+ * the same status again.
+ *
+ * @return time of last status update in millis since last reboot
+ */
+ public abstract long onGetStatusUpdateTime();
+
+ /**
+ * Notifies the location provider that clients are listening for locations.
+ * Called with enable set to true when the first client is added and
+ * called with enable set to false when the last client is removed.
+ * This allows the provider to prepare for receiving locations,
+ * and to shut down when no clients are remaining.
+ *
+ * @param enable true if location tracking should be enabled.
+ */
+ public abstract void onEnableLocationTracking(boolean enable);
+
+ /**
+ * Notifies the location provider of the smallest minimum time between updates amongst
+ * all clients that are listening for locations. This allows the provider to reduce
+ * the frequency of updates to match the requested frequency.
+ *
+ * @param minTime the smallest minTime value over all listeners for this provider.
+ */
+ public abstract void onSetMinTime(long minTime);
+
+ /**
+ * Updates the network state for the given provider. This function must
+ * be overwritten if {@link #requiresNetwork} returns true. The state is
+ * {@link #TEMPORARILY_UNAVAILABLE} (disconnected), OR {@link #AVAILABLE}
+ * (connected or connecting).
+ *
+ * @param state data state
+ */
+ public abstract void onUpdateNetworkState(int state, NetworkInfo info);
+
+ /**
+ * Informs the provider when a new location has been computed by a different
+ * location provider. This is intended to be used as aiding data for the
+ * receiving provider.
+ *
+ * @param location new location from other location provider
+ */
+ public abstract void onUpdateLocation(Location location);
+
+ /**
+ * Implements addditional location provider specific additional commands.
+ *
+ * @param command name of the command to send to the provider.
+ * @param extras optional arguments for the command (or null).
+ * The provider may optionally fill the extras Bundle with results from the command.
+ *
+ * @return true if the command succeeds.
+ */
+ public abstract boolean onSendExtraCommand(String command, Bundle extras);
+
+ /**
+ * Notifies the location provider when a new client is listening for locations.
+ *
+ * @param uid user ID of the new client.
+ */
+ public abstract void onAddListener(int uid);
+
+ /**
+ * Notifies the location provider when a client is no longer listening for locations.
+ *
+ * @param uid user ID of the client no longer listening.
+ */
+ public abstract void onRemoveListener(int uid);
+}
+
diff --git a/location/java/com/android/internal/location/GeocoderProxy.java b/location/java/com/android/internal/location/GeocoderProxy.java
new file mode 100644
index 0000000..b06297b
--- /dev/null
+++ b/location/java/com/android/internal/location/GeocoderProxy.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.location;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.location.Address;
+import android.location.GeocoderParams;
+import android.location.IGeocodeProvider;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * A class for proxying IGeocodeProvider implementations.
+ *
+ * {@hide}
+ */
+public class GeocoderProxy {
+
+ private static final String TAG = "GeocoderProxy";
+
+ private final Context mContext;
+ private final Intent mIntent;
+ private final Connection mServiceConnection = new Connection();
+ private IGeocodeProvider mProvider;
+
+ public GeocoderProxy(Context context, String serviceName) {
+ mContext = context;
+ mIntent = new Intent(serviceName);
+ mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private class Connection implements ServiceConnection {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "onServiceConnected " + className);
+ synchronized (this) {
+ mProvider = IGeocodeProvider.Stub.asInterface(service);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "onServiceDisconnected " + className);
+ synchronized (this) {
+ mProvider = null;
+ }
+ }
+ }
+
+ public String getFromLocation(double latitude, double longitude, int maxResults,
+ GeocoderParams params, List<Address> addrs) {
+ IGeocodeProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ return provider.getFromLocation(latitude, longitude, maxResults,
+ params, addrs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getFromLocation failed", e);
+ }
+ }
+ return "Service not Available";
+ }
+
+ public String getFromLocationName(String locationName,
+ double lowerLeftLatitude, double lowerLeftLongitude,
+ double upperRightLatitude, double upperRightLongitude, int maxResults,
+ GeocoderParams params, List<Address> addrs) {
+ IGeocodeProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ return provider.getFromLocationName(locationName, lowerLeftLatitude,
+ lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+ maxResults, params, addrs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getFromLocationName failed", e);
+ }
+ }
+ return "Service not Available";
+ }
+}
diff --git a/location/java/com/android/internal/location/LocationProviderProxy.java b/location/java/com/android/internal/location/LocationProviderProxy.java
index 2e0be89..361104f 100644
--- a/location/java/com/android/internal/location/LocationProviderProxy.java
+++ b/location/java/com/android/internal/location/LocationProviderProxy.java
@@ -16,155 +16,229 @@
package com.android.internal.location;
-import android.location.Address;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.location.ILocationProvider;
import android.location.Location;
-import android.location.LocationManager;
import android.net.NetworkInfo;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
-import java.util.List;
-
/**
- * A class for proxying remote ILocationProvider implementations.
+ * A class for proxying ILocationProvider implementations.
*
* {@hide}
*/
-public class LocationProviderProxy implements IBinder.DeathRecipient {
+public class LocationProviderProxy {
private static final String TAG = "LocationProviderProxy";
+ private final Context mContext;
private final String mName;
- private final ILocationProvider mProvider;
+ private ILocationProvider mProvider;
+ private Intent mIntent;
+ private Handler mHandler;
+ private final Connection mServiceConnection = new Connection();
+
+ // cached values set by the location manager
private boolean mLocationTracking = false;
private boolean mEnabled = false;
- private long mMinTime = 0;
- private boolean mDead;
+ private long mMinTime = -1;
+ private int mNetworkState;
+ private NetworkInfo mNetworkInfo;
- public LocationProviderProxy(String name, ILocationProvider provider) {
+ // for caching requiresNetwork, requiresSatellite, etc.
+ private DummyLocationProvider mCachedAttributes;
+
+ // constructor for proxying built-in location providers
+ public LocationProviderProxy(Context context, String name, ILocationProvider provider) {
+ mContext = context;
mName = name;
mProvider = provider;
- try {
- provider.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "linkToDeath failed", e);
- mDead = true;
+ }
+
+ // constructor for proxying location providers implemented in a separate service
+ public LocationProviderProxy(Context context, String name, String serviceName,
+ Handler handler) {
+ mContext = context;
+ mName = name;
+ mIntent = new Intent(serviceName);
+ mHandler = handler;
+ mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private class Connection implements ServiceConnection {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className);
+ synchronized (this) {
+ mProvider = ILocationProvider.Stub.asInterface(service);
+ if (mProvider != null) {
+ mHandler.post(mServiceConnectedTask);
+ }
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className);
+ synchronized (this) {
+ mProvider = null;
+ }
}
}
- public void unlinkProvider() {
- if (mProvider != null) {
- mProvider.asBinder().unlinkToDeath(this, 0);
+ private Runnable mServiceConnectedTask = new Runnable() {
+ public void run() {
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ if (provider == null) {
+ return;
+ }
+ }
+
+ if (mCachedAttributes == null) {
+ try {
+ mCachedAttributes = new DummyLocationProvider(mName);
+ mCachedAttributes.setRequiresNetwork(provider.requiresNetwork());
+ mCachedAttributes.setRequiresSatellite(provider.requiresSatellite());
+ mCachedAttributes.setRequiresCell(provider.requiresCell());
+ mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost());
+ mCachedAttributes.setSupportsAltitude(provider.supportsAltitude());
+ mCachedAttributes.setSupportsSpeed(provider.supportsSpeed());
+ mCachedAttributes.setSupportsBearing(provider.supportsBearing());
+ mCachedAttributes.setPowerRequirement(provider.getPowerRequirement());
+ mCachedAttributes.setAccuracy(provider.getAccuracy());
+ } catch (RemoteException e) {
+ mCachedAttributes = null;
+ }
+ }
+
+ // resend previous values from the location manager if the service has restarted
+ try {
+ if (mEnabled) {
+ provider.enable();
+ }
+ if (mLocationTracking) {
+ provider.enableLocationTracking(true);
+ }
+ if (mMinTime >= 0) {
+ provider.setMinTime(mMinTime);
+ }
+ if (mNetworkInfo != null) {
+ provider.updateNetworkState(mNetworkState, mNetworkInfo);
+ }
+ } catch (RemoteException e) {
+ }
}
- }
+ };
public String getName() {
return mName;
}
- public boolean isDead() {
- return mDead;
- }
-
public boolean requiresNetwork() {
- try {
- return mProvider.requiresNetwork();
- } catch (RemoteException e) {
- Log.e(TAG, "requiresNetwork failed", e);
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.requiresNetwork();
+ } else {
return false;
}
}
public boolean requiresSatellite() {
- try {
- return mProvider.requiresSatellite();
- } catch (RemoteException e) {
- Log.e(TAG, "requiresSatellite failed", e);
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.requiresSatellite();
+ } else {
return false;
}
}
public boolean requiresCell() {
- try {
- return mProvider.requiresCell();
- } catch (RemoteException e) {
- Log.e(TAG, "requiresCell failed", e);
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.requiresCell();
+ } else {
return false;
}
}
public boolean hasMonetaryCost() {
- try {
- return mProvider.hasMonetaryCost();
- } catch (RemoteException e) {
- Log.e(TAG, "hasMonetaryCost failed", e);
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.hasMonetaryCost();
+ } else {
return false;
}
}
public boolean supportsAltitude() {
- try {
- return mProvider.supportsAltitude();
- } catch (RemoteException e) {
- Log.e(TAG, "supportsAltitude failed", e);
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.supportsAltitude();
+ } else {
return false;
}
}
public boolean supportsSpeed() {
- try {
- return mProvider.supportsSpeed();
- } catch (RemoteException e) {
- Log.e(TAG, "supportsSpeed failed", e);
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.supportsSpeed();
+ } else {
return false;
}
}
public boolean supportsBearing() {
- try {
- return mProvider.supportsBearing();
- } catch (RemoteException e) {
- Log.e(TAG, "supportsBearing failed", e);
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.supportsBearing();
+ } else {
return false;
}
}
public int getPowerRequirement() {
- try {
- return mProvider.getPowerRequirement();
- } catch (RemoteException e) {
- Log.e(TAG, "getPowerRequirement failed", e);
- return 0;
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.getPowerRequirement();
+ } else {
+ return -1;
}
}
public int getAccuracy() {
- try {
- return mProvider.getAccuracy();
- } catch (RemoteException e) {
- Log.e(TAG, "getAccuracy failed", e);
- return 0;
+ if (mCachedAttributes != null) {
+ return mCachedAttributes.getAccuracy();
+ } else {
+ return -1;
}
}
public void enable() {
- try {
- mProvider.enable();
- mEnabled = true;
- } catch (RemoteException e) {
- Log.e(TAG, "enable failed", e);
+ mEnabled = true;
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ provider.enable();
+ } catch (RemoteException e) {
+ }
}
}
public void disable() {
- try {
- mProvider.disable();
- mEnabled = false;
- } catch (RemoteException e) {
- Log.e(TAG, "disable failed", e);
+ mEnabled = false;
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ provider.disable();
+ } catch (RemoteException e) {
+ }
}
}
@@ -173,22 +247,32 @@
}
public int getStatus(Bundle extras) {
- try {
- return mProvider.getStatus(extras);
- } catch (RemoteException e) {
- Log.e(TAG, "getStatus failed", e);
- return 0;
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
}
+ if (provider != null) {
+ try {
+ return provider.getStatus(extras);
+ } catch (RemoteException e) {
+ }
+ }
+ return 0;
}
public long getStatusUpdateTime() {
- try {
- return mProvider.getStatusUpdateTime();
- } catch (RemoteException e) {
- Log.e(TAG, "getStatusUpdateTime failed", e);
- return 0;
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
}
- }
+ if (provider != null) {
+ try {
+ return provider.getStatusUpdateTime();
+ } catch (RemoteException e) {
+ }
+ }
+ return 0;
+ }
public boolean isLocationTracking() {
return mLocationTracking;
@@ -196,10 +280,18 @@
public void enableLocationTracking(boolean enable) {
mLocationTracking = enable;
- try {
- mProvider.enableLocationTracking(enable);
- } catch (RemoteException e) {
- Log.e(TAG, "enableLocationTracking failed", e);
+ if (!enable) {
+ mMinTime = -1;
+ }
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ provider.enableLocationTracking(enable);
+ } catch (RemoteException e) {
+ }
}
}
@@ -208,58 +300,84 @@
}
public void setMinTime(long minTime) {
- mMinTime = minTime;
- try {
- mProvider.setMinTime(minTime);
- } catch (RemoteException e) {
- Log.e(TAG, "setMinTime failed", e);
+ mMinTime = minTime;
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ provider.setMinTime(minTime);
+ } catch (RemoteException e) {
+ }
}
}
public void updateNetworkState(int state, NetworkInfo info) {
- try {
- mProvider.updateNetworkState(state, info);
- } catch (RemoteException e) {
- Log.e(TAG, "updateNetworkState failed", e);
+ mNetworkState = state;
+ mNetworkInfo = info;
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ provider.updateNetworkState(state, info);
+ } catch (RemoteException e) {
+ }
}
}
public void updateLocation(Location location) {
- try {
- mProvider.updateLocation(location);
- } catch (RemoteException e) {
- Log.e(TAG, "updateLocation failed", e);
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ provider.updateLocation(location);
+ } catch (RemoteException e) {
+ }
}
}
public boolean sendExtraCommand(String command, Bundle extras) {
- try {
- return mProvider.sendExtraCommand(command, extras);
- } catch (RemoteException e) {
- Log.e(TAG, "sendExtraCommand failed", e);
- return false;
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
}
+ if (provider != null) {
+ try {
+ provider.sendExtraCommand(command, extras);
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
}
public void addListener(int uid) {
- try {
- mProvider.addListener(uid);
- } catch (RemoteException e) {
- Log.e(TAG, "addListener failed", e);
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
+ }
+ if (provider != null) {
+ try {
+ provider.addListener(uid);
+ } catch (RemoteException e) {
+ }
}
}
public void removeListener(int uid) {
- try {
- mProvider.removeListener(uid);
- } catch (RemoteException e) {
- Log.e(TAG, "removeListener failed", e);
+ ILocationProvider provider;
+ synchronized (mServiceConnection) {
+ provider = mProvider;
}
- }
-
- public void binderDied() {
- Log.w(TAG, "Location Provider " + mName + " died");
- mDead = true;
- mProvider.asBinder().unlinkToDeath(this, 0);
+ if (provider != null) {
+ try {
+ provider.removeListener(uid);
+ } catch (RemoteException e) {
+ }
+ }
}
}
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
new file mode 100644
index 0000000..ce56443
--- /dev/null
+++ b/media/java/android/media/CamcorderProfile.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.
+ */
+
+package android.media;
+
+/**
+ * The CamcorderProfile class is used to retrieve the
+ * predefined camcorder profile settings for camcorder applications.
+ * The compressed output from a recording session with a given
+ * CamcorderProfile contains two tracks: one for auido and one for video.
+ *
+ * <p>Each profile specifies the following set of parameters:
+ * <ul>
+ * <li> The file output format, @see android.media.MediaRecorder.OutputFormat
+ * <li> Video codec format, @see android.media.MediaRecorder.VideoEncoder
+ * <li> Video bit rate in bits per second
+ * <li> Video frame rate in frames per second
+ * <li> Video frame width and height,
+ * <li> Audio codec format, @see android.media.MediaRecorder.AudioEncoder
+ * <li> Audio bit rate in bits per second,
+ * <li> Audio sample rate
+ * <li> Number of audio channels for recording.
+ * </ul>
+ * {@hide}
+ */
+public class CamcorderProfile
+{
+
+ /**
+ * The Quality class represents the quality level of each CamcorderProfile.
+ *
+ * The output from recording sessions with high quality level usually may have
+ * larger output bit rate, better video and/or audio recording quality, and
+ * laerger video frame resolution and higher audio sampling rate, etc, than those
+ * with low quality level.
+ */
+ public enum Quality {
+ /* Do not change these values/ordinals without updating their counterpart
+ * in include/media/MediaProfiles.h!
+ */
+ HIGH,
+ LOW
+ };
+
+ /**
+ * The quality level of the camcorder profile
+ * @see android.media.CamcorderProfile.Quality
+ */
+ public final Quality mQuality;
+
+ /**
+ * The file output format of the camcorder profile
+ * @see android.media.MediaRecorder.OutputFormat
+ */
+ public final int mFileFormat;
+
+ /**
+ * The video encoder being used for the video track
+ * @see android.media.MediaRecorder.VideoEncoder
+ */
+ public final int mVideoCodec;
+
+ /**
+ * The target video output bit rate in bits per second
+ */
+ public final int mVideoBitRate;
+
+ /**
+ * The target video frame rate in frames per second
+ */
+ public final int mVideoFrameRate;
+
+ /**
+ * The target video frame width in pixels
+ */
+ public final int mVideoFrameWidth;
+
+ /**
+ * The target video frame height in pixels
+ */
+ public final int mVideoFrameHeight;
+
+ /**
+ * The audio encoder being used for the audio track.
+ * @see android.media.MediaRecorder.AudioEncoder
+ */
+ public final int mAudioCodec;
+
+ /**
+ * The target audio output bit rate in bits per second
+ */
+ public final int mAudioBitRate;
+
+ /**
+ * The audio sampling rate used for the audio track
+ */
+ public final int mAudioSampleRate;
+
+ /**
+ * The number of audio channels used for the audio track
+ */
+ public final int mAudioChannels;
+
+ /**
+ * Returns the camcorder profile for the given quality.
+ * @param quality the target quality level for the camcorder profile
+ * @see android.media.CamcorderProfile.Quality
+ */
+ public static CamcorderProfile get(Quality quality) {
+ return native_get_camcorder_profile(quality.ordinal());
+ }
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+
+ // Private constructor called by JNI
+ private CamcorderProfile(int quality,
+ int fileFormat,
+ int videoCodec,
+ int videoBitRate,
+ int videoFrameRate,
+ int videoWidth,
+ int videoHeight,
+ int audioCodec,
+ int audioBitRate,
+ int audioSampleRate,
+ int audioChannels) {
+
+ mQuality = Quality.values()[quality];
+ mFileFormat = fileFormat;
+ mVideoCodec = videoCodec;
+ mVideoBitRate = videoBitRate;
+ mVideoFrameRate = videoFrameRate;
+ mVideoFrameWidth = videoWidth;
+ mVideoFrameHeight = videoHeight;
+ mAudioCodec = audioCodec;
+ mAudioBitRate = audioBitRate;
+ mAudioSampleRate = audioSampleRate;
+ mAudioChannels = audioChannels;
+ }
+
+ // Methods implemented by JNI
+ private static native final void native_init();
+ private static native final CamcorderProfile native_get_camcorder_profile(int quality);
+}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index e9d3372..4292754 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -28,22 +28,37 @@
* This is a class for reading and writing Exif tags in a JPEG file.
*/
public class ExifInterface {
-
// The Exif tag names
+ /** Type is int. */
public static final String TAG_ORIENTATION = "Orientation";
+ /** Type is String. */
public static final String TAG_DATETIME = "DateTime";
+ /** Type is String. */
public static final String TAG_MAKE = "Make";
+ /** Type is String. */
public static final String TAG_MODEL = "Model";
+ /** Type is int. */
public static final String TAG_FLASH = "Flash";
+ /** Type is int. */
public static final String TAG_IMAGE_WIDTH = "ImageWidth";
+ /** Type is int. */
public static final String TAG_IMAGE_LENGTH = "ImageLength";
+ /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
public static final String TAG_GPS_LATITUDE = "GPSLatitude";
+ /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
+ /** Type is String. */
public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+ /** Type is String. */
public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+ /** Type is String. */
public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
+ /** Type is String. */
public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
+ /** Type is int. */
public static final String TAG_WHITE_BALANCE = "WhiteBalance";
+ /** Type is rational. */
+ public static final String TAG_FOCAL_LENGTH = "FocalLength";
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
@@ -114,6 +129,29 @@
}
/**
+ * Returns the double value of the specified rational tag. If there is no
+ * such tag in the JPEG file or the value cannot be parsed as double, return
+ * <var>defaultValue</var>.
+ *
+ * @param tag the name of the tag.
+ * @param defaultValue the value to return if the tag is not available.
+ */
+ public double getAttributeDouble(String tag, double defaultValue) {
+ String value = mAttributes.get(tag);
+ if (value == null) return defaultValue;
+ try {
+ int index = value.indexOf("/");
+ if (index == -1) return defaultValue;
+ double denom = Double.parseDouble(value.substring(index + 1));
+ if (denom == 0) return defaultValue;
+ double num = Double.parseDouble(value.substring(0, index));
+ return num / denom;
+ } catch (NumberFormatException ex) {
+ return defaultValue;
+ }
+ }
+
+ /**
* Set the value of the specified tag.
*
* @param tag the name of the tag.
diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index cd3ad88..50380c1 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -162,7 +162,54 @@
return cap;
}
-static JNINativeMethod gMethods[] = {
+static jobject
+android_media_MediaProfiles_native_get_camcorder_profile(JNIEnv *env, jobject thiz, jint quality)
+{
+ LOGV("native_get_camcorder_profile: %d", quality);
+ if (quality != CAMCORDER_QUALITY_HIGH && quality != CAMCORDER_QUALITY_LOW) {
+ jniThrowException(env, "java/lang/RuntimeException", "Unknown camcorder profile quality");
+ return NULL;
+ }
+
+ camcorder_quality q = static_cast<camcorder_quality>(quality);
+ int fileFormat = sProfiles->getCamcorderProfileParamByName("file.format", q);
+ int videoCodec = sProfiles->getCamcorderProfileParamByName("vid.codec", q);
+ int videoBitRate = sProfiles->getCamcorderProfileParamByName("vid.bps", q);
+ int videoFrameRate = sProfiles->getCamcorderProfileParamByName("vid.fps", q);
+ int videoFrameWidth = sProfiles->getCamcorderProfileParamByName("vid.width", q);
+ int videoFrameHeight = sProfiles->getCamcorderProfileParamByName("vid.height", q);
+ int audioCodec = sProfiles->getCamcorderProfileParamByName("aud.codec", q);
+ int audioBitRate = sProfiles->getCamcorderProfileParamByName("aud.bps", q);
+ int audioSampleRate = sProfiles->getCamcorderProfileParamByName("aud.hz", q);
+ int audioChannels = sProfiles->getCamcorderProfileParamByName("aud.ch", q);
+
+ // Check on the values retrieved
+ if (fileFormat == -1 || videoCodec == -1 || audioCodec == -1 ||
+ videoBitRate == -1 || videoFrameRate == -1 || videoFrameWidth == -1 || videoFrameHeight == -1 ||
+ audioBitRate == -1 || audioSampleRate == -1 || audioChannels == -1) {
+
+ jniThrowException(env, "java/lang/RuntimeException", "Error retrieving camcorder profile params");
+ return NULL;
+ }
+
+ jclass camcorderProfileClazz = env->FindClass("android/media/CamcorderProfile");
+ jmethodID camcorderProfileConstructorMethodID = env->GetMethodID(camcorderProfileClazz, "<init>", "(IIIIIIIIIII)V");
+ return env->NewObject(camcorderProfileClazz,
+ camcorderProfileConstructorMethodID,
+ quality,
+ fileFormat,
+ videoCodec,
+ videoBitRate,
+ videoFrameRate,
+ videoFrameWidth,
+ videoFrameHeight,
+ audioCodec,
+ audioBitRate,
+ audioSampleRate,
+ audioChannels);
+}
+
+static JNINativeMethod gMethodsForEncoderCapabilitiesClass[] = {
{"native_init", "()V", (void *)android_media_MediaProfiles_native_init},
{"native_get_num_file_formats", "()I", (void *)android_media_MediaProfiles_native_get_num_file_formats},
{"native_get_file_format", "(I)I", (void *)android_media_MediaProfiles_native_get_file_format},
@@ -176,12 +223,29 @@
(void *)android_media_MediaProfiles_native_get_audio_encoder_cap},
};
-static const char* const kClassPathName = "android/media/MediaProfiles";
+static JNINativeMethod gMethodsForCamcorderProfileClass[] = {
+ {"native_init", "()V", (void *)android_media_MediaProfiles_native_init},
+ {"native_get_camcorder_profile", "(I)Landroid/media/CamcorderProfile;",
+ (void *)android_media_MediaProfiles_native_get_camcorder_profile},
+};
+
+static const char* const kEncoderCapabilitiesClassPathName = "android/media/EncoderCapabilities";
+static const char* const kCamcorderProfileClassPathName = "android/media/CamcorderProfile";
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaProfiles(JNIEnv *env)
{
- return AndroidRuntime::registerNativeMethods(env,
- "android/media/EncoderCapabilities", gMethods, NELEM(gMethods));
+ int ret1 = AndroidRuntime::registerNativeMethods(env,
+ kEncoderCapabilitiesClassPathName,
+ gMethodsForEncoderCapabilitiesClass,
+ NELEM(gMethodsForEncoderCapabilitiesClass));
+
+ int ret2 = AndroidRuntime::registerNativeMethods(env,
+ kCamcorderProfileClassPathName,
+ gMethodsForCamcorderProfileClass,
+ NELEM(gMethodsForCamcorderProfileClass));
+
+ // Success if ret1 == 0 && ret2 == 0
+ return (ret1 || ret2);
}
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index d51ab30..12872bc 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -54,12 +54,20 @@
LOCAL_SHARED_LIBRARIES += libdl
endif
-LOCAL_C_INCLUDES := external/tremor/Tremor \
+LOCAL_C_INCLUDES := \
$(JNI_H_INCLUDE) \
$(call include-path-for, graphics corecg) \
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
$(TOP)/frameworks/base/media/libstagefright/include
+ifeq ($(TARGET_ARCH),arm)
+ LOCAL_C_INCLUDES += \
+ $(TOP)/external/tremolo/Tremolo
+else
+ LOCAL_C_INCLUDES += \
+ $(TOP)/external/tremor/Tremor
+endif
+
LOCAL_MODULE:= libmediaplayerservice
include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp
index f42d55b..1bfcf65 100644
--- a/media/libmediaplayerservice/StagefrightPlayer.cpp
+++ b/media/libmediaplayerservice/StagefrightPlayer.cpp
@@ -49,30 +49,11 @@
}
status_t StagefrightPlayer::prepare() {
- LOGV("prepare");
-
- int32_t width, height;
- if (mPlayer->getVideoDimensions(&width, &height) != OK) {
- width = height = 0;
- }
-
- sendEvent(MEDIA_SET_VIDEO_SIZE, width, height);
-
- return OK;
+ return mPlayer->prepare();
}
status_t StagefrightPlayer::prepareAsync() {
- LOGV("prepareAsync");
-
- status_t err = prepare();
-
- if (err != OK) {
- return err;
- }
-
- sendEvent(MEDIA_PREPARED);
-
- return OK;
+ return mPlayer->prepareAsync();
}
status_t StagefrightPlayer::start() {
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index dbb52c6..58eb12e 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -48,8 +48,15 @@
LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
- $(TOP)/external/opencore/android \
+ $(TOP)/external/opencore/android
+
+ifeq ($(TARGET_ARCH),arm)
+ LOCAL_C_INCLUDES += \
+ $(TOP)/external/tremolo/Tremolo
+else
+ LOCAL_C_INCLUDES += \
$(TOP)/external/tremor/Tremor
+endif
LOCAL_SHARED_LIBRARIES := \
libbinder \
@@ -60,8 +67,6 @@
libsonivox \
libvorbisidec
-ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
-
LOCAL_STATIC_LIBRARIES := \
libstagefright_aacdec \
libstagefright_amrnbdec \
@@ -69,12 +74,18 @@
libstagefright_amrwbdec \
libstagefright_avcdec \
libstagefright_m4vh263dec \
- libstagefright_mp3dec \
- libstagefright_id3
+ libstagefright_mp3dec
LOCAL_SHARED_LIBRARIES += \
libstagefright_amrnb_common \
- libstagefright_avc_common \
+ libstagefright_avc_common
+
+ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
+
+LOCAL_STATIC_LIBRARIES += \
+ libstagefright_id3
+
+LOCAL_SHARED_LIBRARIES += \
libstagefright_color_conversion
endif
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
index 01578c1..4926920 100644
--- a/media/libstagefright/AudioPlayer.cpp
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -37,6 +37,7 @@
mPositionTimeMediaUs(-1),
mPositionTimeRealUs(-1),
mSeeking(false),
+ mReachedEOS(false),
mStarted(false),
mAudioSink(audioSink) {
}
@@ -47,12 +48,6 @@
}
}
-void AudioPlayer::setListenerCallback(
- void (*notify)(void *cookie, int what), void *cookie) {
- mListenerCallback = notify;
- mListenerCookie = cookie;
-}
-
void AudioPlayer::setSource(const sp<MediaSource> &source) {
CHECK_EQ(mSource, NULL);
mSource = source;
@@ -172,6 +167,7 @@
mPositionTimeMediaUs = -1;
mPositionTimeRealUs = -1;
mSeeking = false;
+ mReachedEOS = false;
mStarted = false;
}
@@ -180,6 +176,16 @@
static_cast<AudioPlayer *>(user)->AudioCallback(event, info);
}
+bool AudioPlayer::isSeeking() {
+ Mutex::Autolock autoLock(mLock);
+ return mSeeking;
+}
+
+bool AudioPlayer::reachedEOS() {
+ Mutex::Autolock autoLock(mLock);
+ return mReachedEOS;
+}
+
// static
void AudioPlayer::AudioSinkCallback(
MediaPlayerBase::AudioSink *audioSink,
@@ -203,6 +209,11 @@
LOGV("AudioCallback");
}
+ if (mReachedEOS) {
+ memset(data, 0, size);
+ return;
+ }
+
size_t size_done = 0;
size_t size_remaining = size;
while (size_remaining > 0) {
@@ -218,6 +229,8 @@
mInputBuffer->release();
mInputBuffer = NULL;
}
+
+ mSeeking = false;
}
}
@@ -227,30 +240,19 @@
CHECK((err == OK && mInputBuffer != NULL)
|| (err != OK && mInputBuffer == NULL));
- if (mSeeking) {
- mSeeking = false;
-
- if (mListenerCallback) {
- (*mListenerCallback)(mListenerCookie, SEEK_COMPLETE);
- }
- }
+ Mutex::Autolock autoLock(mLock);
if (err != OK) {
- if (mListenerCallback) {
- (*mListenerCallback)(mListenerCookie, REACHED_EOS);
- }
-
+ mReachedEOS = true;
memset((char *)data + size_done, 0, size_remaining);
break;
}
- Mutex::Autolock autoLock(mLock);
CHECK(mInputBuffer->meta_data()->findInt64(
kKeyTime, &mPositionTimeMediaUs));
mPositionTimeRealUs =
- -mLatencyUs
- + ((mNumFramesPlayed + size_done / mFrameSize) * 1000000)
+ ((mNumFramesPlayed + size_done / mFrameSize) * 1000000)
/ mSampleRate;
LOGV("buffer->size() = %d, "
@@ -302,7 +304,12 @@
return 0;
}
- return mPositionTimeMediaUs + (getRealTimeUsLocked() - mPositionTimeRealUs);
+ int64_t realTimeOffset = getRealTimeUsLocked() - mPositionTimeRealUs;
+ if (realTimeOffset < 0) {
+ realTimeOffset = 0;
+ }
+
+ return mPositionTimeMediaUs + realTimeOffset;
}
bool AudioPlayer::getMediaTimeMapping(
@@ -319,6 +326,7 @@
Mutex::Autolock autoLock(mLock);
mSeeking = true;
+ mReachedEOS = false;
mSeekTimeUs = time_us;
return OK;
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 4e7738e..d25f7f6 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -37,21 +37,23 @@
namespace android {
struct AwesomeEvent : public TimedEventQueue::Event {
- AwesomeEvent(AwesomePlayer *player, int32_t code)
+ AwesomeEvent(
+ AwesomePlayer *player,
+ void (AwesomePlayer::*method)())
: mPlayer(player),
- mCode(code) {
+ mMethod(method) {
}
protected:
virtual ~AwesomeEvent() {}
virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
- mPlayer->onEvent(mCode);
+ (mPlayer->*mMethod)();
}
private:
AwesomePlayer *mPlayer;
- int32_t mCode;
+ void (AwesomePlayer::*mMethod)();
AwesomeEvent(const AwesomeEvent &);
AwesomeEvent &operator=(const AwesomeEvent &);
@@ -115,13 +117,18 @@
DataSource::RegisterDefaultSniffers();
- mVideoEvent = new AwesomeEvent(this, 0);
+ mVideoEvent = new AwesomeEvent(this, &AwesomePlayer::onVideoEvent);
mVideoEventPending = false;
- mStreamDoneEvent = new AwesomeEvent(this, 1);
+ mStreamDoneEvent = new AwesomeEvent(this, &AwesomePlayer::onStreamDone);
mStreamDoneEventPending = false;
- mBufferingEvent = new AwesomeEvent(this, 2);
+ mBufferingEvent = new AwesomeEvent(this, &AwesomePlayer::onBufferingUpdate);
mBufferingEventPending = false;
+ mCheckAudioStatusEvent = new AwesomeEvent(
+ this, &AwesomePlayer::onCheckAudioStatus);
+
+ mAudioStatusEventPending = false;
+
mQueue.start();
reset();
@@ -140,6 +147,8 @@
mVideoEventPending = false;
mQueue.cancelEvent(mStreamDoneEvent->eventID());
mStreamDoneEventPending = false;
+ mQueue.cancelEvent(mCheckAudioStatusEvent->eventID());
+ mAudioStatusEventPending = false;
if (!keepBufferingGoing) {
mQueue.cancelEvent(mBufferingEvent->eventID());
@@ -283,35 +292,12 @@
mPrefetcher.clear();
}
-// static
-void AwesomePlayer::AudioNotify(void *_me, int what) {
- AwesomePlayer *me = (AwesomePlayer *)_me;
-
- Mutex::Autolock autoLock(me->mLock);
-
- switch (what) {
- case AudioPlayer::REACHED_EOS:
- me->postStreamDoneEvent_l();
- break;
-
- case AudioPlayer::SEEK_COMPLETE:
- {
- me->notifyListener_l(MEDIA_SEEK_COMPLETE);
- break;
- }
-
- default:
- CHECK(!"should not be here.");
- break;
- }
-}
-
-void AwesomePlayer::notifyListener_l(int msg, int ext1) {
+void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) {
if (mListener != NULL) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
- listener->sendEvent(msg, ext1);
+ listener->sendEvent(msg, ext1, ext2);
}
}
}
@@ -323,7 +309,7 @@
if (mDurationUs >= 0) {
int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs();
int64_t positionUs = 0;
- if (mVideoRenderer != NULL) {
+ if (mVideoSource != NULL) {
positionUs = mVideoTimeUs;
} else if (mAudioPlayer != NULL) {
positionUs = mAudioPlayer->getMediaTimeUs();
@@ -347,7 +333,7 @@
if (mFlags & LOOPING) {
seekTo_l(0);
- if (mVideoRenderer != NULL) {
+ if (mVideoSource != NULL) {
postVideoEvent_l();
}
} else {
@@ -373,10 +359,6 @@
if (mAudioPlayer == NULL) {
if (mAudioSink != NULL) {
mAudioPlayer = new AudioPlayer(mAudioSink);
-
- mAudioPlayer->setListenerCallback(
- &AwesomePlayer::AudioNotify, this);
-
mAudioPlayer->setSource(mAudioSource);
status_t err = mAudioPlayer->start();
@@ -393,10 +375,15 @@
mTimeSource = mAudioPlayer;
deferredAudioSeek = true;
+
+ mWatchForAudioSeekComplete = false;
+ mWatchForAudioEOS = true;
}
} else {
mAudioPlayer->resume();
}
+
+ postCheckAudioStatusEvent_l();
}
if (mTimeSource == NULL && mAudioPlayer == NULL) {
@@ -404,14 +391,8 @@
}
if (mVideoSource != NULL) {
- if (mVideoRenderer == NULL) {
- initRenderer_l();
- }
-
- if (mVideoRenderer != NULL) {
- // Kick off video playback
- postVideoEvent_l();
- }
+ // Kick off video playback
+ postVideoEvent_l();
}
if (deferredAudioSeek) {
@@ -532,7 +513,7 @@
status_t AwesomePlayer::getPosition(int64_t *positionUs) {
Mutex::Autolock autoLock(mLock);
- if (mVideoRenderer != NULL) {
+ if (mVideoSource != NULL) {
*positionUs = mVideoTimeUs;
} else if (mAudioPlayer != NULL) {
*positionUs = mAudioPlayer->getMediaTimeUs();
@@ -558,9 +539,11 @@
}
void AwesomePlayer::seekAudioIfNecessary_l() {
- if (mSeeking && mVideoRenderer == NULL && mAudioPlayer != NULL) {
+ if (mSeeking && mVideoSource == NULL && mAudioPlayer != NULL) {
mAudioPlayer->seekTo(mSeekTimeUs);
+ mWatchForAudioSeekComplete = true;
+ mWatchForAudioEOS = true;
mSeeking = false;
}
}
@@ -645,15 +628,7 @@
return mVideoSource != NULL ? OK : UNKNOWN_ERROR;
}
-void AwesomePlayer::onEvent(int32_t code) {
- if (code == 1) {
- onStreamDone();
- return;
- } else if (code == 2) {
- onBufferingUpdate();
- return;
- }
-
+void AwesomePlayer::onVideoEvent() {
Mutex::Autolock autoLock(mLock);
mVideoEventPending = false;
@@ -687,7 +662,9 @@
if (err == INFO_FORMAT_CHANGED) {
LOGV("VideoSource signalled format change.");
- initRenderer_l();
+ if (mVideoRenderer != NULL) {
+ initRenderer_l();
+ }
continue;
}
@@ -718,6 +695,8 @@
LOGV("seeking audio to %lld us (%.2f secs).", timeUs, timeUs / 1E6);
mAudioPlayer->seekTo(timeUs);
+ mWatchForAudioSeekComplete = true;
+ mWatchForAudioEOS = true;
} else {
// If we're playing video only, report seek complete now,
// otherwise audio player will notify us later.
@@ -762,7 +741,13 @@
return;
}
- mVideoRenderer->render(mVideoBuffer);
+ if (mVideoRenderer == NULL) {
+ initRenderer_l();
+ }
+
+ if (mVideoRenderer != NULL) {
+ mVideoRenderer->render(mVideoBuffer);
+ }
if (mLastVideoBuffer) {
mLastVideoBuffer->release();
@@ -803,5 +788,90 @@
mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
}
+void AwesomePlayer::postCheckAudioStatusEvent_l() {
+ if (mAudioStatusEventPending) {
+ return;
+ }
+ mAudioStatusEventPending = true;
+ mQueue.postEventWithDelay(mCheckAudioStatusEvent, 100000ll);
+}
+
+void AwesomePlayer::onCheckAudioStatus() {
+ Mutex::Autolock autoLock(mLock);
+ mAudioStatusEventPending = false;
+
+ if (mWatchForAudioSeekComplete && !mAudioPlayer->isSeeking()) {
+ mWatchForAudioSeekComplete = false;
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ }
+
+ if (mWatchForAudioEOS && mAudioPlayer->reachedEOS()) {
+ mWatchForAudioEOS = false;
+ postStreamDoneEvent_l();
+ }
+
+ postCheckAudioStatusEvent_l();
+}
+
+status_t AwesomePlayer::prepare() {
+ Mutex::Autolock autoLock(mLock);
+
+ status_t err = prepareAsync_l();
+
+ if (err != OK) {
+ return err;
+ }
+
+ while (mAsyncPrepareEvent != NULL) {
+ mPreparedCondition.wait(mLock);
+ }
+
+ return OK;
+}
+
+status_t AwesomePlayer::prepareAsync() {
+ Mutex::Autolock autoLock(mLock);
+ return prepareAsync_l();
+}
+
+status_t AwesomePlayer::prepareAsync_l() {
+ if (mAsyncPrepareEvent != NULL) {
+ return UNKNOWN_ERROR; // async prepare already pending.
+ }
+
+ mAsyncPrepareEvent = new AwesomeEvent(
+ this, &AwesomePlayer::onPrepareAsyncEvent);
+
+ mQueue.postEvent(mAsyncPrepareEvent);
+
+ return OK;
+}
+
+void AwesomePlayer::onPrepareAsyncEvent() {
+ sp<Prefetcher> prefetcher;
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ prefetcher = mPrefetcher;
+ }
+
+ if (prefetcher != NULL) {
+ prefetcher->prepare();
+ }
+
+ Mutex::Autolock autoLock(mLock);
+
+ if (mVideoWidth < 0 || mVideoHeight < 0) {
+ notifyListener_l(MEDIA_SET_VIDEO_SIZE, 0, 0);
+ } else {
+ notifyListener_l(MEDIA_SET_VIDEO_SIZE, mVideoWidth, mVideoHeight);
+ }
+
+ notifyListener_l(MEDIA_PREPARED);
+
+ mAsyncPrepareEvent = NULL;
+ mPreparedCondition.signal();
+}
+
} // namespace android
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index accd94d..79b7674 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -737,6 +737,7 @@
{ kKeyYear, "TYE", "TYER" },
{ kKeyAuthor, "TXT", "TEXT" },
{ kKeyCDTrackNumber, "TRK", "TRCK" },
+ { kKeyDiscNumber, "TPA", "TPOS" },
};
static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 90bbdfe..0355a82 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -18,7 +18,6 @@
#define LOG_TAG "OMXCodec"
#include <utils/Log.h>
-#if BUILD_WITH_FULL_STAGEFRIGHT
#include "include/AACDecoder.h"
#include "include/AMRNBDecoder.h"
#include "include/AMRNBEncoder.h"
@@ -26,7 +25,6 @@
#include "include/AVCDecoder.h"
#include "include/M4vH263Decoder.h"
#include "include/MP3Decoder.h"
-#endif
#include "include/ESDS.h"
@@ -56,9 +54,6 @@
const char *codec;
};
-#if BUILD_WITH_FULL_STAGEFRIGHT
-#define OPTIONAL(x,y) { x, y },
-
#define FACTORY_CREATE(name) \
static sp<MediaSource> Make##name(const sp<MediaSource> &source) { \
return new name(source); \
@@ -103,42 +98,30 @@
#undef FACTORY_REF
#undef FACTORY_CREATE
-#else
-#define OPTIONAL(x,y)
-#endif
-
static const CodecInfo kDecoderInfo[] = {
{ MEDIA_MIMETYPE_IMAGE_JPEG, "OMX.TI.JPEG.decode" },
{ MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.TI.MP3.decode" },
- OPTIONAL(MEDIA_MIMETYPE_AUDIO_MPEG, "MP3Decoder")
- { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.PV.mp3dec" },
+ { MEDIA_MIMETYPE_AUDIO_MPEG, "MP3Decoder" },
{ MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.decode" },
- OPTIONAL(MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBDecoder")
- { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.PV.amrdec" },
+ { MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBDecoder" },
{ MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.decode" },
- OPTIONAL(MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBDecoder")
- { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.PV.amrdec" },
+ { MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBDecoder" },
{ MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.decode" },
- OPTIONAL(MEDIA_MIMETYPE_AUDIO_AAC, "AACDecoder")
- { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.PV.aacdec" },
+ { MEDIA_MIMETYPE_AUDIO_AAC, "AACDecoder" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.Decoder" },
- OPTIONAL(MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Decoder")
- { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.PV.mpeg4dec" },
+ { MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Decoder" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.decoder.h263" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.Video.Decoder" },
- OPTIONAL(MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Decoder")
- { MEDIA_MIMETYPE_VIDEO_H263, "OMX.PV.h263dec" },
+ { MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Decoder" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.decoder.avc" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.Decoder" },
- OPTIONAL(MEDIA_MIMETYPE_VIDEO_AVC, "AVCDecoder")
- { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.PV.avcdec" },
+ { MEDIA_MIMETYPE_VIDEO_AVC, "AVCDecoder" },
};
static const CodecInfo kEncoderInfo[] = {
{ MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.encode" },
- OPTIONAL(MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBEncoder")
- { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.PV.amrencnb" },
+ { MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBEncoder" },
{ MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.encode" },
{ MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.encode" },
{ MEDIA_MIMETYPE_AUDIO_AAC, "OMX.PV.aacenc" },
diff --git a/media/libstagefright/Prefetcher.cpp b/media/libstagefright/Prefetcher.cpp
index 862998a..835e167 100644
--- a/media/libstagefright/Prefetcher.cpp
+++ b/media/libstagefright/Prefetcher.cpp
@@ -171,7 +171,7 @@
}
}
-int64_t Prefetcher::getCachedDurationUs() {
+int64_t Prefetcher::getCachedDurationUs(bool *noMoreData) {
Mutex::Autolock autoLock(mLock);
int64_t minCacheDurationUs = -1;
@@ -197,9 +197,25 @@
}
}
+ if (noMoreData) {
+ *noMoreData = minCacheDurationUs < 0;
+ }
+
return minCacheDurationUs < 0 ? 0 : minCacheDurationUs;
}
+status_t Prefetcher::prepare() {
+ // Buffer about 2 secs worth of data on prepare.
+
+ int64_t duration;
+ bool noMoreData;
+ do {
+ duration = getCachedDurationUs(&noMoreData);
+ } while (!noMoreData && duration < 2000000);
+
+ return OK;
+}
+
////////////////////////////////////////////////////////////////////////////////
PrefetchedSource::PrefetchedSource(
@@ -232,15 +248,6 @@
mStarted = true;
- for (;;) {
- // Buffer 2 secs on startup.
- if (mReachedEOS || mCacheDurationUs > 2000000) {
- break;
- }
-
- mCondition.wait(mLock);
- }
-
return OK;
}
diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp
index 3451383..22f701c 100644
--- a/media/libstagefright/StagefrightMediaScanner.cpp
+++ b/media/libstagefright/StagefrightMediaScanner.cpp
@@ -191,6 +191,7 @@
};
static const KeyMap kKeyMap[] = {
{ "tracknumber", METADATA_KEY_CD_TRACK_NUMBER },
+ { "discnumber", METADATA_KEY_DISC_NUMBER },
{ "album", METADATA_KEY_ALBUM },
{ "artist", METADATA_KEY_ARTIST },
{ "composer", METADATA_KEY_COMPOSER },
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index dfba74f..f617fe8 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -305,6 +305,7 @@
static const Map kMap[] = {
{ kKeyMIMEType, METADATA_KEY_MIMETYPE },
{ kKeyCDTrackNumber, METADATA_KEY_CD_TRACK_NUMBER },
+ { kKeyDiscNumber, METADATA_KEY_DISC_NUMBER },
{ kKeyAlbum, METADATA_KEY_ALBUM },
{ kKeyArtist, METADATA_KEY_ARTIST },
{ kKeyAuthor, METADATA_KEY_AUTHOR },
diff --git a/media/libstagefright/codecs/Android.mk b/media/libstagefright/codecs/Android.mk
index 1fa7e93..2e431205 100644
--- a/media/libstagefright/codecs/Android.mk
+++ b/media/libstagefright/codecs/Android.mk
@@ -1,8 +1,4 @@
-ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
-
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
include $(call all-makefiles-under,$(LOCAL_PATH))
-
-endif
diff --git a/media/libstagefright/id3/Android.mk b/media/libstagefright/id3/Android.mk
index 3c47e2e..add8f3c 100644
--- a/media/libstagefright/id3/Android.mk
+++ b/media/libstagefright/id3/Android.mk
@@ -1,3 +1,5 @@
+ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
+
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
@@ -25,3 +27,4 @@
include $(BUILD_EXECUTABLE)
+endif
diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp
index 6d64717..16a2a10 100644
--- a/media/libstagefright/id3/ID3.cpp
+++ b/media/libstagefright/id3/ID3.cpp
@@ -24,6 +24,7 @@
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/Utils.h>
#include <utils/String8.h>
+#include <byteswap.h>
namespace android {
@@ -273,7 +274,9 @@
String8 *s) {
size_t utf8len = 0;
for (size_t i = 0; i < size; ++i) {
- if (data[i] < 0x80) {
+ if (data[i] == '\0') {
+ break;
+ } else if (data[i] < 0x80) {
++utf8len;
} else {
utf8len += 2;
@@ -290,7 +293,9 @@
char *tmp = new char[utf8len];
char *ptr = tmp;
for (size_t i = 0; i < size; ++i) {
- if (data[i] < 0x80) {
+ if (data[i] == '\0') {
+ break;
+ } else if (data[i] < 0x80) {
*ptr++ = data[i];
} else if (data[i] < 0xc0) {
*ptr++ = 0xc2;
@@ -324,7 +329,7 @@
return;
}
- id->setTo((const char *)mFrameData, mFrameSize);
+ convertISO8859ToString8(mFrameData, mFrameSize, id);
return;
}
@@ -336,7 +341,26 @@
} else {
// UCS-2
// API wants number of characters, not number of bytes...
- id->setTo((const char16_t *)(mFrameData + 1), n / 2);
+ int len = n / 2;
+ const char16_t *framedata = (const char16_t *) (mFrameData + 1);
+ char16_t *framedatacopy = NULL;
+ if (*framedata == 0xfffe) {
+ // endianness marker doesn't match host endianness, convert
+ framedatacopy = new char16_t[len];
+ for (int i = 0; i < len; i++) {
+ framedatacopy[i] = bswap_16(framedata[i]);
+ }
+ framedata = framedatacopy;
+ }
+ // If the string starts with an endianness marker, skip it
+ if (*framedata == 0xfeff) {
+ framedata++;
+ len--;
+ }
+ id->setTo(framedata, len);
+ if (framedatacopy != NULL) {
+ delete[] framedatacopy;
+ }
}
}
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 8bd6594..651b910 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -57,6 +57,10 @@
void reset();
+ status_t prepare();
+ status_t prepareAsync();
+ status_t prepareAsync_l();
+
status_t play();
status_t pause();
@@ -109,16 +113,25 @@
bool mSeeking;
int64_t mSeekTimeUs;
+ bool mWatchForAudioSeekComplete;
+ bool mWatchForAudioEOS;
+
sp<TimedEventQueue::Event> mVideoEvent;
bool mVideoEventPending;
sp<TimedEventQueue::Event> mStreamDoneEvent;
bool mStreamDoneEventPending;
sp<TimedEventQueue::Event> mBufferingEvent;
bool mBufferingEventPending;
+ sp<TimedEventQueue::Event> mCheckAudioStatusEvent;
+ bool mAudioStatusEventPending;
+
+ sp<TimedEventQueue::Event> mAsyncPrepareEvent;
+ Condition mPreparedCondition;
void postVideoEvent_l(int64_t delayUs = -1);
void postBufferingEvent_l();
void postStreamDoneEvent_l();
+ void postCheckAudioStatusEvent_l();
MediaBuffer *mLastVideoBuffer;
MediaBuffer *mVideoBuffer;
@@ -137,14 +150,14 @@
status_t setAudioSource(sp<MediaSource> source);
status_t setVideoSource(sp<MediaSource> source);
- void onEvent(int32_t code);
-
- static void AudioNotify(void *me, int what);
void onStreamDone();
- void notifyListener_l(int msg, int ext1 = 0);
+ void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0);
+ void onVideoEvent();
void onBufferingUpdate();
+ void onCheckAudioStatus();
+ void onPrepareAsyncEvent();
AwesomePlayer(const AwesomePlayer &);
AwesomePlayer &operator=(const AwesomePlayer &);
diff --git a/media/libstagefright/include/Prefetcher.h b/media/libstagefright/include/Prefetcher.h
index 7a97785..d227864 100644
--- a/media/libstagefright/include/Prefetcher.h
+++ b/media/libstagefright/include/Prefetcher.h
@@ -34,7 +34,9 @@
// that will benefit from prefetching/caching the original one.
sp<MediaSource> addSource(const sp<MediaSource> &source);
- int64_t getCachedDurationUs();
+ int64_t getCachedDurationUs(bool *noMoreData = NULL);
+
+ status_t prepare();
protected:
virtual ~Prefetcher();
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index 70c6a58..2458d2a 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -129,11 +129,16 @@
sendCommand(OMX_CommandStateSet, OMX_StateIdle);
OMX_ERRORTYPE err;
while ((err = OMX_GetState(mHandle, &state)) == OMX_ErrorNone
- && state != OMX_StateIdle) {
+ && state != OMX_StateIdle
+ && state != OMX_StateInvalid) {
usleep(100000);
}
CHECK_EQ(err, OMX_ErrorNone);
+ if (state == OMX_StateInvalid) {
+ break;
+ }
+
// fall through
}
@@ -146,7 +151,8 @@
OMX_ERRORTYPE err;
while ((err = OMX_GetState(mHandle, &state)) == OMX_ErrorNone
- && state != OMX_StateLoaded) {
+ && state != OMX_StateLoaded
+ && state != OMX_StateInvalid) {
LOGV("waiting for Loaded state...");
usleep(100000);
}
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index 51fcaf5..c05d90a 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -512,7 +512,9 @@
sp<MediaExtractor> extractor = CreateExtractorFromURI(url);
- CHECK(extractor != NULL);
+ if (extractor == NULL) {
+ return NULL;
+ }
for (size_t i = 0; i < extractor->countTracks(); ++i) {
sp<MetaData> meta = extractor->getTrackMetaData(i);
@@ -571,6 +573,10 @@
sp<MediaSource> source = CreateSourceForMime(mime);
sp<MediaSource> seekSource = CreateSourceForMime(mime);
+ if (source == NULL || seekSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
CHECK_EQ(seekSource->start(), OK);
sp<MediaSource> codec = OMXCodec::Create(
diff --git a/native/graphics/jni/Android.mk b/native/graphics/jni/Android.mk
new file mode 100644
index 0000000..b7ae4a9
--- /dev/null
+++ b/native/graphics/jni/Android.mk
@@ -0,0 +1,36 @@
+BASE_PATH := $(call my-dir)
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PRELINK_MODULE := false
+
+# setup for skia optimizations
+#
+ifneq ($(ARCH_ARM_HAVE_VFP),true)
+ LOCAL_CFLAGS += -DSK_SOFTWARE_FLOAT
+endif
+
+ifeq ($(ARCH_ARM_HAVE_NEON),true)
+ LOCAL_CFLAGS += -D__ARM_HAVE_NEON
+endif
+
+# our source files
+#
+LOCAL_SRC_FILES:= \
+ bitmap.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libandroid_runtime \
+ libskia
+
+LOCAL_C_INCLUDES += \
+ external/skia/include/core \
+ frameworks/base/native/include \
+ frameworks/base/core/jni/android/graphics \
+ dalvik/libnativehelper/include/nativehelper
+
+LOCAL_MODULE:= libjnigraphics
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp
new file mode 100644
index 0000000..fd73430
--- /dev/null
+++ b/native/graphics/jni/bitmap.cpp
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+#include <android/bitmap.h>
+#include <GraphicsJNI.h>
+
+int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
+ AndroidBitmapInfo* info) {
+ if (NULL == env || NULL == jbitmap) {
+ return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+ }
+
+ SkBitmap* bm = GraphicsJNI::getNativeBitmap(env, jbitmap);
+ if (NULL == bm) {
+ return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
+ }
+
+ if (info) {
+ info->width = bm->width();
+ info->height = bm->height();
+ info->stride = bm->rowBytes();
+ info->flags = 0;
+
+ switch (bm->config()) {
+ case SkBitmap::kARGB_8888_Config:
+ info->format = ANDROID_BITMAP_FORMAT_RGBA_8888;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ info->format = ANDROID_BITMAP_FORMAT_RGB_565;
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ info->format = ANDROID_BITMAP_FORMAT_RGBA_4444;
+ break;
+ case SkBitmap::kA8_Config:
+ info->format = ANDROID_BITMAP_FORMAT_A_8;
+ break;
+ default:
+ info->format = ANDROID_BITMAP_FORMAT_NONE;
+ break;
+ }
+ }
+ return ANDROID_BITMAP_RESUT_SUCCESS;
+}
+
+int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) {
+ if (NULL == env || NULL == jbitmap) {
+ return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+ }
+
+ SkBitmap* bm = GraphicsJNI::getNativeBitmap(env, jbitmap);
+ if (NULL == bm) {
+ return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
+ }
+
+ bm->lockPixels();
+ void* addr = bm->getPixels();
+ if (NULL == addr) {
+ bm->unlockPixels();
+ return ANDROID_BITMAP_RESULT_ALLOCATION_FAILED;
+ }
+
+ if (addrPtr) {
+ *addrPtr = addr;
+ }
+ return ANDROID_BITMAP_RESUT_SUCCESS;
+}
+
+int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) {
+ if (NULL == env || NULL == jbitmap) {
+ return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+ }
+
+ SkBitmap* bm = GraphicsJNI::getNativeBitmap(env, jbitmap);
+ if (NULL == bm) {
+ return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
+ }
+
+ bm->unlockPixels();
+ return ANDROID_BITMAP_RESUT_SUCCESS;
+}
+
diff --git a/native/include/android/bitmap.h b/native/include/android/bitmap.h
new file mode 100644
index 0000000..5078277
--- /dev/null
+++ b/native/include/android/bitmap.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_BITMAP_H
+#define ANDROID_BITMAP_H
+
+#include <stdint.h>
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ANDROID_BITMAP_RESUT_SUCCESS 0
+#define ANDROID_BITMAP_RESULT_BAD_PARAMETER -1
+#define ANDROID_BITMAP_RESULT_JNI_EXCEPTION -2
+#define ANDROID_BITMAP_RESULT_ALLOCATION_FAILED -3
+
+enum AndroidBitmapFormat {
+ ANDROID_BITMAP_FORMAT_NONE = 0,
+ ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
+ ANDROID_BITMAP_FORMAT_RGB_565 = 4,
+ ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
+ ANDROID_BITMAP_FORMAT_A_8 = 8,
+};
+
+typedef struct {
+ uint32_t width;
+ uint32_t height;
+ uint32_t stride;
+ int32_t format;
+ uint32_t flags; // 0 for now
+} AndroidBitmapInfo;
+
+/**
+ * Given a java bitmap object, fill out the AndroidBitmap struct for it.
+ * If the call fails, the info parameter will be ignored
+ */
+int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
+ AndroidBitmapInfo* info);
+
+/**
+ * Given a java bitmap object, attempt to lock the pixel address.
+ * Locking will ensure that the memory for the pixels will not move
+ * until the unlockPixels call, and ensure that, if the pixels had been
+ * previously purged, they will have been restored.
+ *
+ * If this call succeeds, it must be balanced by a call to
+ * AndroidBitmap_unlockPixels, after which time the address of the pixels should
+ * no longer be used.
+ *
+ * If this succeeds, *addrPtr will be set to the pixel address. If the call
+ * fails, addrPtr will be ignored.
+ */
+int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
+
+/**
+ * Call this to balanace a successful call to AndroidBitmap_lockPixels
+ */
+int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp
index 81864bd..b6e0aae 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -2092,7 +2092,20 @@
if (native_buffer->common.version != sizeof(android_native_buffer_t))
return setError(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR);
-
+
+ switch (native_buffer->format) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_RGB_888:
+ case HAL_PIXEL_FORMAT_RGB_565:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ case HAL_PIXEL_FORMAT_RGBA_5551:
+ case HAL_PIXEL_FORMAT_RGBA_4444:
+ break;
+ default:
+ return setError(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR);
+ }
+
native_buffer->common.incRef(&native_buffer->common);
return (EGLImageKHR)native_buffer;
}
diff --git a/opengl/libagl/light.cpp b/opengl/libagl/light.cpp
index f211bca..ca715db 100644
--- a/opengl/libagl/light.cpp
+++ b/opengl/libagl/light.cpp
@@ -217,18 +217,26 @@
{
uint32_t en = c->lighting.enabledLights;
// Vector from object to viewer, in eye coordinates
- const vec4_t eyeViewer = { 0, 0, 0x1000, 0 };
while (en) {
const int i = 31 - gglClz(en);
en &= ~(1<<i);
light_t& l = c->lighting.lights[i];
+#if OBJECT_SPACE_LIGHTING
c->transforms.mvui.point4(&c->transforms.mvui,
&l.objPosition, &l.position);
+#else
+ l.objPosition = l.position;
+#endif
vnorm3(l.normalizedObjPosition.v, l.objPosition.v);
- c->transforms.mvui.point4(&c->transforms.mvui,
- &l.objViewer, &eyeViewer);
- vnorm3(l.objViewer.v, l.objViewer.v);
}
+ const vec4_t eyeViewer = { 0, 0, 0x10000, 0 };
+#if OBJECT_SPACE_LIGHTING
+ c->transforms.mvui.point3(&c->transforms.mvui,
+ &c->lighting.objViewer, &eyeViewer);
+ vnorm3(c->lighting.objViewer.v, c->lighting.objViewer.v);
+#else
+ c->lighting.objViewer = eyeViewer;
+#endif
}
static inline void validate_light(ogles_context_t* c)
@@ -337,6 +345,7 @@
{
// emission and ambient for the whole scene
vec4_t r = c->lighting.implicitSceneEmissionAndAmbient;
+ const vec4_t objViewer = c->lighting.objViewer;
uint32_t en = c->lighting.enabledLights;
if (ggl_likely(en)) {
@@ -347,7 +356,11 @@
c->arrays.normal.fetch(c, n.v,
c->arrays.normal.element(v->index & vertex_cache_t::INDEX_MASK));
- // TODO: right now we handle GL_RESCALE_NORMALS as if ti were
+#if !OBJECT_SPACE_LIGHTING
+ c->transforms.mvui.point3(&c->transforms.mvui, &n, &n);
+#endif
+
+ // TODO: right now we handle GL_RESCALE_NORMALS as if it were
// GL_NORMALIZE. We could optimize this by scaling mvui
// appropriately instead.
if (c->transforms.rescaleNormals)
@@ -380,13 +393,13 @@
s = dot3(n.v, d.v);
s = (s<0) ? (twoSide?(-s):0) : s;
vsa3(t.v, l.implicitDiffuse.v, s, l.implicitAmbient.v);
-
+
// specular
if (ggl_unlikely(s && l.implicitSpecular.v[3])) {
vec4_t h;
- h.x = d.x + l.objViewer.x;
- h.y = d.y + l.objViewer.y;
- h.z = d.z + l.objViewer.z;
+ h.x = d.x + objViewer.x;
+ h.y = d.y + objViewer.y;
+ h.z = d.z + objViewer.z;
vnorm3(h.v, h.v);
s = dot3(n.v, h.v);
s = (s<0) ? (twoSide?(-s):0) : s;
@@ -515,15 +528,18 @@
case GL_POSITION: {
ogles_validate_transform(c, transform_state_t::MODELVIEW);
transform_t& mv = c->transforms.modelview.transform;
- memcpy(light.position.v, params, sizeof(light.position.v));
- mv.point4(&mv, &light.position, &light.position);
+ mv.point4(&mv, &light.position, reinterpret_cast<vec4_t const*>(params));
invalidate_lighting(c);
return;
}
case GL_SPOT_DIRECTION: {
+#if OBJECT_SPACE_LIGHTING
ogles_validate_transform(c, transform_state_t::MVUI);
transform_t& mvui = c->transforms.mvui;
- mvui.point3(&mvui, &light.spotDir, (vec4_t*)params);
+ mvui.point3(&mvui, &light.spotDir, reinterpret_cast<vec4_t const*>(params));
+#else
+ light.spotDir = *reinterpret_cast<vec4_t const*>(params);
+#endif
vnorm3(light.normalizedSpotDir.v, light.spotDir.v);
invalidate_lighting(c);
return;
@@ -748,8 +764,8 @@
case GL_SPECULAR: what = c->lighting.front.specular.v; break;
case GL_EMISSION: what = c->lighting.front.emission.v; break;
case GL_AMBIENT_AND_DIFFUSE:
- what = c->lighting.front.ambient.v; break;
- other = c->lighting.front.diffuse.v; break;
+ what = c->lighting.front.ambient.v;
+ other = c->lighting.front.diffuse.v;
break;
case GL_SHININESS:
c->lighting.front.shininess = gglFloatToFixed(params[0]);
@@ -788,8 +804,8 @@
case GL_SPECULAR: what = c->lighting.front.specular.v; break;
case GL_EMISSION: what = c->lighting.front.emission.v; break;
case GL_AMBIENT_AND_DIFFUSE:
- what = c->lighting.front.ambient.v; break;
- other= c->lighting.front.diffuse.v; break;
+ what = c->lighting.front.ambient.v;
+ other = c->lighting.front.diffuse.v;
break;
case GL_SHININESS:
c->lighting.front.shininess = gglFloatToFixed(params[0]);
diff --git a/opengl/libagl/light.h b/opengl/libagl/light.h
index 6dae25f..39e3309 100644
--- a/opengl/libagl/light.h
+++ b/opengl/libagl/light.h
@@ -22,6 +22,13 @@
#include <stddef.h>
#include <sys/types.h>
+
+// Set to 1 for object-space lighting evaluation.
+// There are still some bugs with object-space lighting,
+// especially visible in the San Angeles demo.
+#define OBJECT_SPACE_LIGHTING 0
+
+
namespace android {
namespace gl {
diff --git a/opengl/libagl/matrix.cpp b/opengl/libagl/matrix.cpp
index 3c50977..9520f04 100644
--- a/opengl/libagl/matrix.cpp
+++ b/opengl/libagl/matrix.cpp
@@ -55,6 +55,7 @@
static void point2__generic(transform_t const*, vec4_t* c, vec4_t const* o);
static void point3__generic(transform_t const*, vec4_t* c, vec4_t const* o);
static void point4__generic(transform_t const*, vec4_t* c, vec4_t const* o);
+static void point3__mvui(transform_t const*, vec4_t* c, vec4_t const* o);
static void point4__mvui(transform_t const*, vec4_t* c, vec4_t const* o);
// ----------------------------------------------------------------------------
@@ -209,7 +210,7 @@
{
flags = 0;
ops = OP_ALL;
- point3 = point4__mvui;
+ point3 = point3__mvui;
point4 = point4__mvui;
}
@@ -600,17 +601,31 @@
GLfloat r[16];
const GLfloat* const mv = modelview.top().elements();
- // TODO: we need a faster invert, especially for when the modelview
- // is a rigid-body matrix
+ /*
+ When evaluating the lighting equation in eye-space, normals
+ are transformed by the upper 3x3 modelview inverse-transpose.
+ http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node26.html
+
+ (note that inverse-transpose is distributive).
+ Also note that:
+ l(obj) = inv(modelview).l(eye) for local light
+ l(obj) = tr(modelview).l(eye) for infinite light
+ */
+
invert(r, mv);
GLfixed* const x = mvui.matrix.m;
- for (int i=0 ; i<4 ; i++) {
- x[I(i,0)] = gglFloatToFixed(r[I(i,0)]);
- x[I(i,1)] = gglFloatToFixed(r[I(i,1)]);
- x[I(i,2)] = gglFloatToFixed(r[I(i,2)]);
- x[I(i,4)] = gglFloatToFixed(r[I(i,3)]);
- }
+
+#if OBJECT_SPACE_LIGHTING
+ for (int i=0 ; i<4 ; i++)
+ for (int j=0 ; j<4 ; j++)
+ x[I(i,j)] = gglFloatToFixed(r[I(i,j)]);
+#else
+ for (int i=0 ; i<4 ; i++)
+ for (int j=0 ; j<4 ; j++)
+ x[I(i,j)] = gglFloatToFixed(r[I(j,i)]);
+#endif
+
mvui.picker();
}
@@ -739,8 +754,22 @@
lhs->w = mla4(rx, m[ 3], ry, m[ 7], rz, m[11], rw, m[15]);
}
+void point3__mvui(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) {
+ // this is used for transforming light positions back to object space.
+ // w is used as a switch for directional lights, so we need
+ // to preserve it.
+ const GLfixed* const m = mx->matrix.m;
+ const GLfixed rx = rhs->x;
+ const GLfixed ry = rhs->y;
+ const GLfixed rz = rhs->z;
+ lhs->x = mla3(rx, m[ 0], ry, m[ 4], rz, m[ 8]);
+ lhs->y = mla3(rx, m[ 1], ry, m[ 5], rz, m[ 9]);
+ lhs->z = mla3(rx, m[ 2], ry, m[ 6], rz, m[10]);
+ lhs->w = 0;
+}
+
void point4__mvui(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) {
- // this used for transforming light positions back to object space.
+ // this is used for transforming light positions back to object space.
// w is used as a switch for directional lights, so we need
// to preserve it.
const GLfixed* const m = mx->matrix.m;
diff --git a/opengl/libagl/texture.cpp b/opengl/libagl/texture.cpp
index a1a776f..fa25fa9 100644
--- a/opengl/libagl/texture.cpp
+++ b/opengl/libagl/texture.cpp
@@ -1628,6 +1628,11 @@
return;
}
+ if (image == EGL_NO_IMAGE_KHR) {
+ ogles_error(c, GL_INVALID_VALUE);
+ return;
+ }
+
android_native_buffer_t* native_buffer = (android_native_buffer_t*)image;
if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) {
ogles_error(c, GL_INVALID_VALUE);
@@ -1652,4 +1657,26 @@
void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image)
{
+ ogles_context_t* c = ogles_context_t::get();
+ if (target != GL_RENDERBUFFER_OES) {
+ ogles_error(c, GL_INVALID_ENUM);
+ return;
+ }
+
+ if (image == EGL_NO_IMAGE_KHR) {
+ ogles_error(c, GL_INVALID_VALUE);
+ return;
+ }
+
+ android_native_buffer_t* native_buffer = (android_native_buffer_t*)image;
+ if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) {
+ ogles_error(c, GL_INVALID_VALUE);
+ return;
+ }
+ if (native_buffer->common.version != sizeof(android_native_buffer_t)) {
+ ogles_error(c, GL_INVALID_VALUE);
+ return;
+ }
+
+ // well, we're not supporting this extension anyways
}
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index d2f8ced..145e25e 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -166,7 +166,8 @@
uint32_t magic;
DisplayImpl disp[IMPL_NUM_IMPLEMENTATIONS];
EGLint numTotalConfigs;
- volatile int32_t refs;
+ uint32_t refs;
+ Mutex lock;
egl_display_t() : magic('_dpy'), numTotalConfigs(0) { }
~egl_display_t() { magic = 0; }
@@ -644,7 +645,9 @@
egl_display_t * const dp = get_display(dpy);
if (!dp) return setError(EGL_BAD_DISPLAY, EGL_FALSE);
- if (android_atomic_inc(&dp->refs) > 0) {
+ Mutex::Autolock _l(dp->lock);
+
+ if (dp->refs > 0) {
if (major != NULL) *major = VERSION_MAJOR;
if (minor != NULL) *minor = VERSION_MINOR;
return EGL_TRUE;
@@ -728,6 +731,7 @@
}
if (res == EGL_TRUE) {
+ dp->refs++;
if (major != NULL) *major = VERSION_MAJOR;
if (minor != NULL) *minor = VERSION_MINOR;
return EGL_TRUE;
@@ -743,7 +747,15 @@
egl_display_t* const dp = get_display(dpy);
if (!dp) return setError(EGL_BAD_DISPLAY, EGL_FALSE);
- if (android_atomic_dec(&dp->refs) != 1)
+
+ Mutex::Autolock _l(dp->lock);
+
+ if (dp->refs == 0) {
+ return setError(EGL_NOT_INITIALIZED, EGL_FALSE);
+ }
+
+ // this is specific to Android, display termination is ref-counted.
+ if (dp->refs > 1)
return EGL_TRUE;
EGLBoolean res = EGL_FALSE;
@@ -767,6 +779,7 @@
// TODO: all egl_object_t should be marked for termination
+ dp->refs--;
dp->numTotalConfigs = 0;
clearTLS();
return res;
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index d23b7d0..fecd366 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -6,8 +6,8 @@
import android.net.Uri;
import android.os.Debug;
import android.os.IBinder;
-import android.os.IMountService;
-import android.os.MountServiceResultCode;
+import android.os.storage.IMountService;
+import android.os.storage.StorageResultCode;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
@@ -158,12 +158,12 @@
int rc = mountService.createSecureContainer(
containerId, mbLen, "vfat", sdEncKey, ownerUid);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format("Container creation failed (%d)", rc));
// XXX: This destroy should not be necessary
rc = mountService.destroySecureContainer(containerId);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format("Container creation-cleanup failed (%d)", rc));
return null;
}
@@ -171,7 +171,7 @@
// XXX: Does this ever actually succeed?
rc = mountService.createSecureContainer(
containerId, mbLen, "vfat", sdEncKey, ownerUid);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format("Container creation retry failed (%d)", rc));
}
}
@@ -226,7 +226,7 @@
private String mountSdDir(String containerId, String key) {
try {
int rc = getMountService().mountSecureContainer(containerId, key, Process.myUid());
- if (rc == MountServiceResultCode.OperationSucceeded) {
+ if (rc == StorageResultCode.OperationSucceeded) {
return getMountService().getSecureContainerPath(containerId);
} else {
Log.e(TAG, String.format("Failed to mount id %s with rc %d ", containerId, rc));
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java
index 1efa5a3..bca736a2e 100755
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ b/packages/TtsService/src/android/tts/TtsService.java
@@ -133,6 +133,7 @@
private HashMap<String, ITtsCallback> mCallbacksMap;
private Boolean mIsSpeaking;
+ private Boolean mSynthBusy;
private ArrayList<SpeechItem> mSpeechQueue;
private HashMap<String, SoundResource> mEarcons;
private HashMap<String, SoundResource> mUtterances;
@@ -168,6 +169,7 @@
mSelf = this;
mIsSpeaking = false;
+ mSynthBusy = false;
mEarcons = new HashMap<String, SoundResource>();
mUtterances = new HashMap<String, SoundResource>();
@@ -733,10 +735,11 @@
try {
synthAvailable = synthesizerLock.tryLock();
if (!synthAvailable) {
+ mSynthBusy = true;
Thread.sleep(100);
Thread synth = (new Thread(new SynthThread()));
- //synth.setPriority(Thread.MIN_PRIORITY);
synth.start();
+ mSynthBusy = false;
return;
}
int streamType = DEFAULT_STREAM_TYPE;
@@ -802,8 +805,8 @@
}
if (synthAvailable) {
synthesizerLock.unlock();
+ processSpeechQueue();
}
- processSpeechQueue();
}
}
}
@@ -821,10 +824,11 @@
try {
synthAvailable = synthesizerLock.tryLock();
if (!synthAvailable) {
+ mSynthBusy = true;
Thread.sleep(100);
Thread synth = (new Thread(new SynthThread()));
- //synth.setPriority(Thread.MIN_PRIORITY);
synth.start();
+ mSynthBusy = false;
return;
}
String language = "";
@@ -882,8 +886,8 @@
}
if (synthAvailable) {
synthesizerLock.unlock();
+ processSpeechQueue();
}
- processSpeechQueue();
}
}
}
@@ -959,6 +963,12 @@
private void processSpeechQueue() {
boolean speechQueueAvailable = false;
+ synchronized (this) {
+ if (mSynthBusy){
+ // There is already a synth thread waiting to run.
+ return;
+ }
+ }
try {
speechQueueAvailable =
speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
diff --git a/preloaded-classes b/preloaded-classes
index 0c84904..092b539 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -22,14 +22,14 @@
android.app.ActivityThread$ProviderRefCount
android.app.AlertDialog
android.app.Application
-android.app.ApplicationContext
-android.app.ApplicationContext$ApplicationContentResolver
-android.app.ApplicationContext$ApplicationPackageManager
-android.app.ApplicationContext$ApplicationPackageManager$PackageRemovedReceiver
-android.app.ApplicationContext$ApplicationPackageManager$ResourceName
-android.app.ApplicationContext$SharedPreferencesImpl
android.app.ApplicationLoaders
android.app.ApplicationThreadNative
+android.app.ContextImpl
+android.app.ContextImpl$ApplicationContentResolver
+android.app.ContextImpl$ApplicationPackageManager
+android.app.ContextImpl$ApplicationPackageManager$PackageRemovedReceiver
+android.app.ContextImpl$ApplicationPackageManager$ResourceName
+android.app.ContextImpl$SharedPreferencesImpl
android.app.Dialog
android.app.ExpandableListActivity
android.app.IActivityManager
@@ -464,7 +464,6 @@
android.webkit.CallbackProxy
android.webkit.CookieManager
android.webkit.CookieSyncManager
-android.webkit.HttpDateTime
android.webkit.JWebCoreJavaBridge
android.webkit.LoadListener
android.webkit.MimeTypeMap
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java
index b56b13b..407983d 100644
--- a/services/java/com/android/server/AccessibilityManagerService.java
+++ b/services/java/com/android/server/AccessibilityManagerService.java
@@ -176,8 +176,8 @@
context.registerReceiver(broadcastReceiver, packageFilter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(broadcastReceiver, sdFilter);
// boot completed
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index f330651..44cc0bb 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -242,6 +242,14 @@
setRepeating(type, bucketTime, interval, operation);
}
+ public void setTime(long millis) {
+ mContext.enforceCallingOrSelfPermission(
+ "android.permission.SET_TIME",
+ "setTime");
+
+ SystemClock.setCurrentTimeMillis(millis);
+ }
+
public void setTimeZone(String tz) {
mContext.enforceCallingOrSelfPermission(
"android.permission.SET_TIME_ZONE",
@@ -774,7 +782,7 @@
mContext.registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(this, sdFilter);
}
@@ -783,7 +791,7 @@
synchronized (mLock) {
String action = intent.getAction();
String pkgList[] = null;
- if (Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else {
Uri data = intent.getData();
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 684f117..f336d1f 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -147,8 +147,8 @@
mContext.registerReceiver(mBroadcastReceiver, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mBroadcastReceiver, sdFilter);
}
@@ -1077,10 +1077,10 @@
} else {
boolean added = false;
String pkgList[] = null;
- if (Intent.ACTION_MEDIA_RESOURCES_AVAILABLE.equals(action)) {
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
added = true;
- } if (Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ } if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
added = false;
} else {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 0562c55..27055ed 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -76,6 +76,7 @@
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "BackupManagerService";
@@ -118,6 +119,7 @@
boolean mEnabled; // access to this is synchronized on 'this'
boolean mProvisioned;
+ boolean mAutoRestore;
PowerManager.WakeLock mWakelock;
HandlerThread mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
BackupHandler mBackupHandler;
@@ -175,11 +177,21 @@
public IBackupTransport transport;
public IRestoreObserver observer;
public long token;
+ public PackageInfo pkgInfo;
+
+ RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
+ long _token, PackageInfo _pkg) {
+ transport = _transport;
+ observer = _obs;
+ token = _token;
+ pkgInfo = _pkg;
+ }
RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token) {
transport = _transport;
observer = _obs;
token = _token;
+ pkgInfo = null;
}
}
@@ -209,10 +221,18 @@
File mJournalDir;
File mJournal;
- // Keep a log of all the apps we've ever backed up
+ // Keep a log of all the apps we've ever backed up, and what the
+ // dataset tokens are for both the current backup dataset and
+ // the ancestral dataset.
private File mEverStored;
HashSet<String> mEverStoredApps = new HashSet<String>();
+ static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1; // increment when the schema changes
+ File mTokenFile;
+ Set<String> mAncestralPackages = null;
+ long mAncestralToken = 0;
+ long mCurrentToken = 0;
+
// Persistently track the need to do a full init
static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
HashSet<String> mPendingInits = new HashSet<String>(); // transport names
@@ -241,8 +261,11 @@
// snapshot the pending-backup set and work on that
ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>();
+ File oldJournal = mJournal;
synchronized (mQueueLock) {
- // Do we have any work to do?
+ // Do we have any work to do? Construct the work queue
+ // then release the synchronization lock to actually run
+ // the backup.
if (mPendingBackups.size() > 0) {
for (BackupRequest b: mPendingBackups.values()) {
queue.add(b);
@@ -251,20 +274,22 @@
mPendingBackups.clear();
// Start a new backup-queue journal file too
- File oldJournal = mJournal;
mJournal = null;
- // At this point, we have started a new journal file, and the old
- // file identity is being passed to the backup processing thread.
- // When it completes successfully, that old journal file will be
- // deleted. If we crash prior to that, the old journal is parsed
- // at next boot and the journaled requests fulfilled.
- (new PerformBackupTask(transport, queue, oldJournal)).run();
- } else {
- Log.v(TAG, "Backup requested but nothing pending");
- mWakelock.release();
}
}
+
+ if (queue.size() > 0) {
+ // At this point, we have started a new journal file, and the old
+ // file identity is being passed to the backup processing thread.
+ // When it completes successfully, that old journal file will be
+ // deleted. If we crash prior to that, the old journal is parsed
+ // at next boot and the journaled requests fulfilled.
+ (new PerformBackupTask(transport, queue, oldJournal)).run();
+ } else {
+ Log.v(TAG, "Backup requested but nothing pending");
+ mWakelock.release();
+ }
break;
}
@@ -276,7 +301,7 @@
RestoreParams params = (RestoreParams)msg.obj;
Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
(new PerformRestoreTask(params.transport, params.observer,
- params.token)).run();
+ params.token, params.pkgInfo)).run();
break;
}
@@ -340,6 +365,8 @@
Settings.Secure.BACKUP_ENABLED, 0) != 0;
mProvisioned = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.BACKUP_PROVISIONED, 0) != 0;
+ mAutoRestore = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.BACKUP_AUTO_RESTORE, 0) != 0;
// If Encrypted file systems is enabled or disabled, this call will return the
// correct directory.
mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup");
@@ -472,6 +499,28 @@
private void initPackageTracking() {
if (DEBUG) Log.v(TAG, "Initializing package tracking");
+ // Remember our ancestral dataset
+ mTokenFile = new File(mBaseStateDir, "ancestral");
+ try {
+ RandomAccessFile tf = new RandomAccessFile(mTokenFile, "r");
+ int version = tf.readInt();
+ if (version == CURRENT_ANCESTRAL_RECORD_VERSION) {
+ mAncestralToken = tf.readLong();
+ mCurrentToken = tf.readLong();
+
+ int numPackages = tf.readInt();
+ if (numPackages >= 0) {
+ mAncestralPackages = new HashSet<String>();
+ for (int i = 0; i < numPackages; i++) {
+ String pkgName = tf.readUTF();
+ mAncestralPackages.add(pkgName);
+ }
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to read token file", e);
+ }
+
// Keep a log of what apps we've ever backed up. Because we might have
// rebooted in the middle of an operation that was removing something from
// this log, we sanity-check its contents here and reconstruct it.
@@ -531,8 +580,8 @@
mContext.registerReceiver(mBroadcastReceiver, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mBroadcastReceiver, sdFilter);
}
@@ -604,6 +653,9 @@
mEverStoredApps.clear();
mEverStored.delete();
+ mCurrentToken = 0;
+ writeRestoreTokens();
+
// Remove all the state files
for (File sf : stateFileDir.listFiles()) {
// ... but don't touch the needs-init sentinel
@@ -687,10 +739,10 @@
}
added = Intent.ACTION_PACKAGE_ADDED.equals(action);
replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
- } else if (Intent.ACTION_MEDIA_RESOURCES_AVAILABLE.equals(action)) {
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
added = true;
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- } else if (Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
added = false;
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
}
@@ -877,7 +929,7 @@
addPackageParticipantsLockedInner(packageName, allApps);
}
- // Called from the backup thread: record that the given app has been successfully
+ // Called from the backup task: record that the given app has been successfully
// backed up at least once
void logBackupComplete(String packageName) {
if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;
@@ -935,6 +987,37 @@
}
}
+ // Persistently record the current and ancestral backup tokens as well
+ // as the set of packages with data [supposedly] available in the
+ // ancestral dataset.
+ void writeRestoreTokens() {
+ try {
+ RandomAccessFile af = new RandomAccessFile(mTokenFile, "rwd");
+
+ // First, the version number of this record, for futureproofing
+ af.writeInt(CURRENT_ANCESTRAL_RECORD_VERSION);
+
+ // Write the ancestral and current tokens
+ af.writeLong(mAncestralToken);
+ af.writeLong(mCurrentToken);
+
+ // Now write the set of ancestral packages
+ if (mAncestralPackages == null) {
+ af.writeInt(-1);
+ } else {
+ af.writeInt(mAncestralPackages.size());
+ if (DEBUG) Log.v(TAG, "Ancestral packages: " + mAncestralPackages.size());
+ for (String pkgName : mAncestralPackages) {
+ af.writeUTF(pkgName);
+ if (DEBUG) Log.v(TAG, " " + pkgName);
+ }
+ }
+ af.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to write token file:", e);
+ }
+ }
+
// Return the given transport
private IBackupTransport getTransport(String transportName) {
synchronized (mTransports) {
@@ -1151,6 +1234,16 @@
Log.e(TAG, "Error in backup thread", e);
status = BackupConstants.TRANSPORT_ERROR;
} finally {
+ // If everything actually went through and this is the first time we've
+ // done a backup, we can now record what the current backup dataset token
+ // is.
+ if ((mCurrentToken == 0) && (status != BackupConstants.TRANSPORT_OK)) {
+ try {
+ mCurrentToken = mTransport.getCurrentRestoreSet();
+ } catch (RemoteException e) { /* cannot happen */ }
+ writeRestoreTokens();
+ }
+
// If things went wrong, we need to re-stage the apps we had expected
// to be backing up in this pass. This journals the package names in
// the current active pending-backup file, not in the we are holding
@@ -1392,6 +1485,7 @@
private IBackupTransport mTransport;
private IRestoreObserver mObserver;
private long mToken;
+ private PackageInfo mTargetPackage;
private File mStateDir;
class RestoreRequest {
@@ -1405,11 +1499,11 @@
}
PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
- long restoreSetToken) {
+ long restoreSetToken, PackageInfo targetPackage) {
mTransport = transport;
- Log.d(TAG, "PerformRestoreThread mObserver=" + mObserver);
mObserver = observer;
mToken = restoreSetToken;
+ mTargetPackage = targetPackage;
try {
mStateDir = new File(mBaseStateDir, transport.transportDirName());
@@ -1421,7 +1515,8 @@
public void run() {
long startRealtime = SystemClock.elapsedRealtime();
if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport
- + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken));
+ + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
+ + " mTargetPackage=" + mTargetPackage);
/**
* Restore sequence:
*
@@ -1438,9 +1533,10 @@
*
* On errors, we try our best to recover and move on to the next
* application, but if necessary we abort the whole operation --
- * the user is waiting, after al.
+ * the user is waiting, after all.
*/
+ PackageManagerBackupAgent pmAgent = null;
int error = -1; // assume error
// build the set of apps to restore
@@ -1456,7 +1552,12 @@
restorePackages.add(omPackage);
List<PackageInfo> agentPackages = allAgentPackages();
- restorePackages.addAll(agentPackages);
+ if (mTargetPackage == null) {
+ restorePackages.addAll(agentPackages);
+ } else {
+ // Just one package to attempt restore of
+ restorePackages.add(mTargetPackage);
+ }
// let the observer know that we're running
if (mObserver != null) {
@@ -1496,7 +1597,7 @@
}
// Pull the Package Manager metadata from the restore set first
- PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
+ pmAgent = new PackageManagerBackupAgent(
mPackageManager, agentPackages);
processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()));
@@ -1638,6 +1739,15 @@
}
}
+ // If this was a restoreAll operation, record that this was our
+ // ancestral dataset, as well as the set of apps that are possibly
+ // restoreable from the dataset
+ if (mTargetPackage == null && pmAgent != null) {
+ mAncestralPackages = pmAgent.getRestoredPackages();
+ mAncestralToken = mToken;
+ writeRestoreTokens();
+ }
+
// done; we can finally release the wakelock
mWakelock.release();
}
@@ -2019,6 +2129,20 @@
}
}
+ // Enable/disable automatic restore of app data at install time
+ public void setAutoRestore(boolean doAutoRestore) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "setBackupEnabled");
+
+ Log.i(TAG, "Auto restore => " + doAutoRestore);
+
+ synchronized (this) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_AUTO_RESTORE, doAutoRestore ? 1 : 0);
+ mAutoRestore = doAutoRestore;
+ }
+ }
+
// Mark the backup service as having been provisioned
public void setBackupProvisioned(boolean available) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -2202,7 +2326,7 @@
}
}
- public synchronized int performRestore(long token, IRestoreObserver observer) {
+ public synchronized int restoreAll(long token, IRestoreObserver observer) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"performRestore");
@@ -2232,6 +2356,55 @@
return -1;
}
+ public synchronized int restorePackage(String packageName, IRestoreObserver observer) {
+ if (DEBUG) Log.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer);
+
+ PackageInfo app = null;
+ try {
+ app = mPackageManager.getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException nnf) {
+ Log.w(TAG, "Asked to restore nonexistent pkg " + packageName);
+ return -1;
+ }
+
+ // If the caller is not privileged and is not coming from the target
+ // app's uid, throw a permission exception back to the caller.
+ int perm = mContext.checkPermission(android.Manifest.permission.BACKUP,
+ Binder.getCallingPid(), Binder.getCallingUid());
+ if ((perm == PackageManager.PERMISSION_DENIED) &&
+ (app.applicationInfo.uid != Binder.getCallingUid())) {
+ Log.w(TAG, "restorePackage: bad packageName=" + packageName
+ + " or calling uid=" + Binder.getCallingUid());
+ throw new SecurityException("No permission to restore other packages");
+ }
+
+ // So far so good; we're allowed to try to restore this package. Now
+ // check whether there is data for it in the current dataset, falling back
+ // to the ancestral dataset if not.
+ long token = mAncestralToken;
+ synchronized (mQueueLock) {
+ if (mEverStoredApps.contains(packageName)) {
+ token = mCurrentToken;
+ }
+ }
+
+ // If we didn't come up with a place to look -- no ancestral dataset and
+ // the app has never been backed up from this device -- there's nothing
+ // to do but return failure.
+ if (token == 0) {
+ return -1;
+ }
+
+ // Ready to go: enqueue the restore request and claim success
+ long oldId = Binder.clearCallingIdentity();
+ mWakelock.acquire();
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(mRestoreTransport, observer, token, app);
+ mBackupHandler.sendMessage(msg);
+ Binder.restoreCallingIdentity(oldId);
+ return 0;
+ }
+
public synchronized void endRestoreSession() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"endRestoreSession");
@@ -2300,6 +2473,12 @@
}
}
+ pw.println("Ancestral packages: "
+ + (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
+ for (String pkg : mAncestralPackages) {
+ pw.println(" " + pkg);
+ }
+
pw.println("Ever backed up: " + mEverStoredApps.size());
for (String pkg : mEverStoredApps) {
pw.println(" " + pkg);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index aa4956f..4259016 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -43,6 +43,8 @@
import com.android.internal.telephony.Phone;
+import com.android.server.connectivity.Tethering;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -62,6 +64,9 @@
private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
"android.telephony.apn-restore";
+
+ private Tethering mTethering;
+
/**
* Sometimes we want to refer to the individual network state
* trackers separately, and sometimes we just want to treat them
@@ -308,6 +313,8 @@
continue;
}
}
+
+ mTethering = new Tethering(mContext);
}
@@ -784,6 +791,13 @@
"ConnectivityService");
}
+ // TODO Make this a special check when it goes public
+ private void enforceTetherChangePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE,
+ "ConnectivityService");
+ }
+
/**
* Handle a {@code DISCONNECTED} event. If this pertains to the non-active
* network, we ignore it. If it is for the active network, we send out a
@@ -1368,4 +1382,28 @@
}
}
}
+
+ // javadoc from interface
+ public boolean tether(String iface) {
+ enforceTetherChangePermission();
+ return mTethering.tether(iface);
+ }
+
+ // javadoc from interface
+ public boolean untether(String iface) {
+ enforceTetherChangePermission();
+ return mTethering.untether(iface);
+ }
+
+ // TODO - move iface listing, queries, etc to new module
+ // javadoc from interface
+ public String[] getTetherableIfaces() {
+ enforceAccessPermission();
+ return mTethering.getTetherableIfaces();
+ }
+
+ public String[] getTetheredIfaces() {
+ enforceAccessPermission();
+ return mTethering.getTetheredIfaces();
+ }
}
diff --git a/services/java/com/android/server/HeadsetObserver.java b/services/java/com/android/server/HeadsetObserver.java
index 9d69564..c94450b 100644
--- a/services/java/com/android/server/HeadsetObserver.java
+++ b/services/java/com/android/server/HeadsetObserver.java
@@ -53,6 +53,19 @@
private final Context mContext;
private final WakeLock mWakeLock; // held while there is a pending route change
+ private boolean mHandleTTY;
+ private int mTTYState;
+ private AudioManager mAudioManager = null;
+
+ // special use of bits in headset state received from kernel made by some
+ // platforms to indicate changes in TTY mode.
+ private static final int BIT_TTY_OFF = 0;
+ private static final int BIT_TTY_FULL = (1 << 2);
+ private static final int BIT_TTY_VCO = (1 << 5);
+ private static final int BIT_TTY_HCO = (1 << 6);
+ private static final int TTY_BITS_MASK = (BIT_TTY_FULL | BIT_TTY_VCO | BIT_TTY_HCO);
+
+
public HeadsetObserver(Context context) {
mContext = context;
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -61,6 +74,11 @@
startObserving(HEADSET_UEVENT_MATCH);
+ // read settings for TTY mode indication method
+ mHandleTTY = context.getResources().getBoolean(
+ com.android.internal.R.bool.tty_mode_uses_headset_events);
+ mTTYState = BIT_TTY_OFF;
+
init(); // set initial status
}
@@ -100,6 +118,39 @@
}
private synchronized final void update(String newName, int newState) {
+ // handle TTY state change first
+ if (mHandleTTY) {
+ int ttyState = newState & TTY_BITS_MASK;
+ if (ttyState != mTTYState) {
+ String ttyMode;
+
+ switch (ttyState) {
+ case BIT_TTY_FULL:
+ ttyMode = "tty_full";
+ break;
+ case BIT_TTY_VCO:
+ ttyMode = "tty_vco";
+ break;
+ case BIT_TTY_HCO:
+ ttyMode = "tty_hco";
+ break;
+ case BIT_TTY_OFF:
+ ttyMode = "tty_off";
+ break;
+ default:
+ ttyMode = "tty_invalid";
+ break;
+
+ }
+ if (ttyMode != "tty_invalid") {
+ mTTYState = ttyState;
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ }
+ mAudioManager.setParameters("tty_mode="+ttyMode);
+ }
+ }
+ }
// Retain only relevant bits
int headsetState = newState & SUPPORTED_HEADSETS;
int newOrOld = headsetState | mHeadsetState;
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index dc942a2..5e96a2f 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -344,8 +344,8 @@
if (pkg != null) {
pkgList = new String[] { pkg };
}
- } else if (Intent.ACTION_MEDIA_RESOURCES_AVAILABLE.equals(action) ||
- Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) ||
+ Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
}
if (pkgList == null || pkgList.length == 0) {
@@ -448,8 +448,8 @@
mContext.registerReceiver(mBroadcastReceiver, packageFilt);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mBroadcastReceiver, sdFilter);
IntentFilter screenOnOffFilt = new IntentFilter();
diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java
index 88b1f02..11297d5 100644
--- a/services/java/com/android/server/Installer.java
+++ b/services/java/com/android/server/Installer.java
@@ -222,6 +222,21 @@
return execute(builder.toString());
}
+ public int rename(String oldname, String newname, boolean useEncryptedFilesystem) {
+ StringBuilder builder = new StringBuilder("rename");
+ builder.append(' ');
+ builder.append(oldname);
+ builder.append(' ');
+ builder.append(newname);
+ builder.append(' ');
+ if (useEncryptedFilesystem) {
+ builder.append('1');
+ } else {
+ builder.append('0');
+ }
+ return execute(builder.toString());
+ }
+
public int deleteCacheFiles(String name, boolean useEncryptedFilesystem) {
StringBuilder builder = new StringBuilder("rmcache");
builder.append(' ');
@@ -308,4 +323,8 @@
return -1;
}
}
+
+ public int moveFiles() {
+ return execute("movefiles");
+ }
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 3a42b37..fff6c54 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -29,16 +29,18 @@
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentQueryMap;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.Cursor;
import android.location.Address;
import android.location.GeocoderParams;
-import android.location.IGeocodeProvider;
import android.location.IGpsStatusListener;
import android.location.IGpsStatusProvider;
import android.location.ILocationListener;
@@ -64,10 +66,11 @@
import android.util.Log;
import android.util.PrintWriterPrinter;
+import com.android.internal.location.GeocoderProxy;
import com.android.internal.location.GpsLocationProvider;
+import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.LocationProviderProxy;
import com.android.internal.location.MockProvider;
-import com.android.internal.location.GpsNetInitiatedHandler;
/**
* The service class that manages LocationProviders and issues location
@@ -105,7 +108,7 @@
private static boolean sProvidersLoaded = false;
private final Context mContext;
- private IGeocodeProvider mGeocodeProvider;
+ private GeocoderProxy mGeocodeProvider;
private IGpsStatusProvider mGpsStatusProvider;
private INetInitiatedListener mNetInitiatedListener;
private LocationWorkerHandler mLocationHandler;
@@ -415,7 +418,6 @@
private void removeProvider(LocationProviderProxy provider) {
mProviders.remove(provider);
- provider.unlinkProvider();
mProvidersByName.remove(provider.getName());
}
@@ -446,11 +448,28 @@
GpsLocationProvider provider = new GpsLocationProvider(mContext, this);
mGpsStatusProvider = provider.getGpsStatusProvider();
mNetInitiatedListener = provider.getNetInitiatedListener();
- LocationProviderProxy proxy = new LocationProviderProxy(LocationManager.GPS_PROVIDER, provider);
+ LocationProviderProxy proxy =
+ new LocationProviderProxy(mContext, LocationManager.GPS_PROVIDER, provider);
addProvider(proxy);
mGpsLocationProvider = proxy;
}
+ // initialize external network location and geocoder services
+ Resources resources = mContext.getResources();
+ String serviceName = resources.getString(
+ com.android.internal.R.string.config_networkLocationProvider);
+ if (serviceName != null) {
+ mNetworkLocationProvider =
+ new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
+ serviceName, mLocationHandler);
+ addProvider(mNetworkLocationProvider);
+ }
+
+ serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
+ if (serviceName != null) {
+ mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
+ }
+
updateProvidersLocked();
}
@@ -484,7 +503,7 @@
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
- IntentFilter sdFilter = new IntentFilter(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mBroadcastReceiver, sdFilter);
// listen for settings changes
@@ -507,45 +526,6 @@
Looper.loop();
}
- public void installLocationProvider(String name, ILocationProvider provider) {
- if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires INSTALL_LOCATION_PROVIDER permission");
- }
-
- synchronized (mLock) {
- // check to see if we are reinstalling a dead provider
- LocationProviderProxy oldProvider = mProvidersByName.get(name);
- if (oldProvider != null) {
- if (oldProvider.isDead()) {
- Log.d(TAG, "replacing dead provider");
- removeProvider(oldProvider);
- } else {
- throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
- }
- }
-
- LocationProviderProxy proxy = new LocationProviderProxy(name, provider);
- addProvider(proxy);
- updateProvidersLocked();
- if (LocationManager.NETWORK_PROVIDER.equals(name)) {
- mNetworkLocationProvider = proxy;
- }
-
- // notify provider of current network state
- proxy.updateNetworkState(mNetworkState, null);
- }
- }
-
- public void installGeocodeProvider(IGeocodeProvider provider) {
- if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires INSTALL_LOCATION_PROVIDER permission");
- }
-
- mGeocodeProvider = provider;
- }
-
private boolean isAllowedBySettingsLocked(String provider) {
if (mEnabledProviders.contains(provider)) {
return true;
@@ -1313,7 +1293,7 @@
}
/**
- * @return null if the provider does not exits
+ * @return null if the provider does not exist
* @throws SecurityException if the provider is not allowed to be
* accessed by the caller
*/
@@ -1332,7 +1312,7 @@
private Bundle _getProviderInfoLocked(String provider) {
LocationProviderProxy p = mProvidersByName.get(provider);
- if (p == null) {
+ if (p == null || !p.isEnabled()) {
return null;
}
@@ -1552,10 +1532,10 @@
if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
- || action.equals(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE)) {
+ || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
synchronized (mLock) {
int uidList[] = null;
- if (action.equals(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE)) {
+ if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
} else {
uidList = new int[]{intent.getIntExtra(Intent.EXTRA_UID, -1)};
@@ -1618,7 +1598,7 @@
synchronized (mLock) {
for (int i = mProviders.size() - 1; i >= 0; i--) {
LocationProviderProxy provider = mProviders.get(i);
- if (provider.requiresNetwork()) {
+ if (provider.isEnabled() && provider.requiresNetwork()) {
provider.updateNetworkState(mNetworkState, info);
}
}
@@ -1669,13 +1649,8 @@
public String getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
if (mGeocodeProvider != null) {
- try {
- return mGeocodeProvider.getFromLocation(latitude, longitude, maxResults,
- params, addrs);
- } catch (RemoteException e) {
- Log.e(TAG, "getFromLocation failed", e);
- mGeocodeProvider = null;
- }
+ return mGeocodeProvider.getFromLocation(latitude, longitude, maxResults,
+ params, addrs);
}
return null;
}
@@ -1687,14 +1662,9 @@
GeocoderParams params, List<Address> addrs) {
if (mGeocodeProvider != null) {
- try {
- return mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
- lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
- maxResults, params, addrs);
- } catch (RemoteException e) {
- Log.e(TAG, "getFromLocationName failed", e);
- mGeocodeProvider = null;
- }
+ return mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
+ lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+ maxResults, params, addrs);
}
return null;
}
@@ -1737,7 +1707,7 @@
if (mProvidersByName.get(name) != null) {
throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
}
- addProvider(new LocationProviderProxy(name, provider));
+ addProvider(new LocationProviderProxy(mContext, name, provider));
mMockProviders.put(name, provider);
mLastKnownLocation.put(name, null);
updateProvidersLocked();
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 6382646..6de2eff 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -23,13 +23,14 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
-import android.os.IMountService;
-import android.os.IMountServiceListener;
-import android.os.MountServiceResultCode;
+import android.os.storage.IMountService;
+import android.os.storage.IMountServiceListener;
+import android.os.storage.StorageResultCode;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Environment;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UEventObserver;
import android.os.Handler;
@@ -41,11 +42,14 @@
import java.io.FileReader;
/**
- * MountService implements an to the mount service daemon
- * @hide
+ * MountService implements back-end services for platform storage
+ * management.
+ * @hide - Applications should use android.os.storage.StorageManager
+ * to access the MountService.
*/
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks {
+ private static final boolean LOCAL_LOGD = false;
private static final String TAG = "MountService";
@@ -109,45 +113,35 @@
private PackageManagerService mPms;
private boolean mUmsEnabling;
private ArrayList<MountServiceBinderListener> mListeners;
+ private boolean mBooted;
+ private boolean mReady;
- /**
- * Constructs a new MountService instance
- *
- * @param context Binder context for this service
- */
- public MountService(Context context) {
- mContext = context;
-
- // XXX: This will go away soon in favor of IMountServiceObserver
- mPms = (PackageManagerService) ServiceManager.getService("package");
-
- // Register a BOOT_COMPLETED handler so that we can start
- // our NativeDaemonConnector. We defer the startup so that we don't
- // start processing events before we ought-to
- mContext.registerReceiver(mBroadcastReceiver,
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
-
- mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
- mListeners = new ArrayList<MountServiceBinderListener>();
+ private void waitForReady() {
+ while (mReady == false) {
+ for (int retries = 5; retries > 0; retries--) {
+ if (mReady) {
+ return;
+ }
+ SystemClock.sleep(1000);
+ }
+ Log.w(TAG, "Waiting too long for mReady!");
+ }
}
- BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- /*
- * Vold does not run in the simulator, so fake out a mounted
- * event to trigger MediaScanner
- */
- if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
- updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
- return;
- }
+ mBooted = true;
- Thread thread = new Thread(
- mConnector, NativeDaemonConnector.class.getName());
- thread.start();
+ String path = Environment.getExternalStorageDirectory().getPath();
+ if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) {
+ int rc = doMountVolume(path);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
+ }
+ }
}
}
};
@@ -161,7 +155,7 @@
}
public void binderDied() {
- Log.d(TAG, "An IMountServiceListener has died!");
+ if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!");
synchronized(mListeners) {
mListeners.remove(this);
mListener.asBinder().unlinkToDeath(this, 0);
@@ -169,7 +163,7 @@
}
}
- int doShareUnshareVolume(String path, String method, boolean enable) {
+ private int doShareUnshareVolume(String path, String method, boolean enable) {
validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
// TODO: Add support for multiple share methods
@@ -182,9 +176,9 @@
*/
String vs = getVolumeState(path);
if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
- mUmsEnabling = enable; // Supress unmounted events
- unmountVolume(path);
- mUmsEnabling = false; // Unsupress unmounted events
+ mUmsEnabling = enable; // Override for isUsbMassStorageEnabled()
+ doUnmountVolume(path);
+ mUmsEnabling = false; // Clear override
}
try {
@@ -192,14 +186,14 @@
"volume %sshare %s %s", (enable ? "" : "un"), path, method));
} catch (NativeDaemonConnectorException e) {
Log.e(TAG, "Failed to share/unshare", e);
- return MountServiceResultCode.OperationFailedInternalError;
+ return StorageResultCode.OperationFailedInternalError;
}
/*
* If we disabled UMS then mount the volume
*/
if (!enable) {
- if (mountVolume(path) != MountServiceResultCode.OperationSucceeded) {
+ if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format(
"Failed to remount %s after disabling share method %s", path, method));
/*
@@ -210,15 +204,19 @@
}
}
- return MountServiceResultCode.OperationSucceeded;
+ return StorageResultCode.OperationSucceeded;
}
- void updatePublicVolumeState(String path, String state) {
+ private void updatePublicVolumeState(String path, String state) {
if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
Log.w(TAG, "Multiple volumes not currently supported");
return;
}
- Log.i(TAG, "State for {" + path + "} = {" + state + "}");
+
+ if (mLegacyState.equals(state)) {
+ Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
+ return;
+ }
String oldState = mLegacyState;
mLegacyState = state;
@@ -227,7 +225,7 @@
for (int i = mListeners.size() -1; i >= 0; i--) {
MountServiceBinderListener bl = mListeners.get(i);
try {
- bl.mListener.onVolumeStateChanged("", path, oldState, state);
+ bl.mListener.onStorageStateChanged(path, oldState, state);
} catch (RemoteException rex) {
Log.e(TAG, "Listener dead");
mListeners.remove(i);
@@ -270,11 +268,7 @@
if (st == VolumeState.NoMedia) {
state = Environment.MEDIA_REMOVED;
} else if (st == VolumeState.Idle) {
- state = null;
- int rc = mountVolume(path);
- if (rc != MountServiceResultCode.OperationSucceeded) {
- Log.e(TAG, String.format("Connection-mount failed (%d)", rc));
- }
+ state = Environment.MEDIA_UNMOUNTED;
} else if (st == VolumeState.Mounted) {
state = Environment.MEDIA_MOUNTED;
Log.i(TAG, "Media already mounted on daemon connection");
@@ -294,23 +288,26 @@
}
try {
- boolean avail = getShareMethodAvailable("ums");
+ boolean avail = doGetShareMethodAvailable("ums");
notifyShareAvailabilityChange("ums", avail);
} catch (Exception ex) {
Log.w(TAG, "Failed to get share availability");
}
+ /*
+ * Now that we've done our initialization, release
+ * the hounds!
+ */
+ mReady = true;
}
}.start();
}
/**
- *
* Callback from NativeDaemonConnector
*/
public boolean onEvent(int code, String raw, String[] cooked) {
Intent in = null;
- // Log.d(TAG, "event {" + raw + "}");
if (code == VoldResponseCode.VolumeStateChange) {
/*
* One of the volumes we're managing has changed state.
@@ -347,34 +344,12 @@
Log.e(TAG, "Failed to parse major/minor", ex);
}
- synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- if (code == VoldResponseCode.VolumeDiskInserted) {
- bl.mListener.onMediaInserted(label, path, major, minor);
- } else if (code == VoldResponseCode.VolumeDiskRemoved) {
- bl.mListener.onMediaRemoved(label, path, major, minor, true);
- } else if (code == VoldResponseCode.VolumeBadRemoval) {
- bl.mListener.onMediaRemoved(label, path, major, minor, false);
- } else {
- Log.e(TAG, String.format("Unknown code {%d}", code));
- }
- } catch (RemoteException rex) {
- Log.e(TAG, "Listener dead");
- mListeners.remove(i);
- } catch (Exception ex) {
- Log.e(TAG, "Listener failed", ex);
- }
- }
- }
-
if (code == VoldResponseCode.VolumeDiskInserted) {
new Thread() {
public void run() {
try {
int rc;
- if ((rc = mountVolume(path)) != MountServiceResultCode.OperationSucceeded) {
+ if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
}
} catch (Exception ex) {
@@ -417,7 +392,7 @@
return true;
}
- void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
+ private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
String vs = getVolumeState(path);
Intent in = null;
@@ -471,7 +446,144 @@
}
}
- void notifyShareAvailabilityChange(String method, final boolean avail) {
+ private boolean doGetShareMethodAvailable(String method) {
+ ArrayList<String> rsp = mConnector.doCommand("share status " + method);
+
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code;
+ try {
+ code = Integer.parseInt(tok[0]);
+ } catch (NumberFormatException nfe) {
+ Log.e(TAG, String.format("Error parsing code %s", tok[0]));
+ return false;
+ }
+ if (code == VoldResponseCode.ShareStatusResult) {
+ if (tok[2].equals("available"))
+ return true;
+ return false;
+ } else {
+ Log.e(TAG, String.format("Unexpected response code %d", code));
+ return false;
+ }
+ }
+ Log.e(TAG, "Got an empty response");
+ return false;
+ }
+
+ private int doMountVolume(String path) {
+ int rc = StorageResultCode.OperationSucceeded;
+
+ try {
+ mConnector.doCommand(String.format("volume mount %s", path));
+ } catch (NativeDaemonConnectorException e) {
+ /*
+ * Mount failed for some reason
+ */
+ Intent in = null;
+ int code = e.getCode();
+ if (code == VoldResponseCode.OpFailedNoMedia) {
+ /*
+ * Attempt to mount but no media inserted
+ */
+ rc = StorageResultCode.OperationFailedNoMedia;
+ } else if (code == VoldResponseCode.OpFailedMediaBlank) {
+ /*
+ * Media is blank or does not contain a supported filesystem
+ */
+ updatePublicVolumeState(path, Environment.MEDIA_NOFS);
+ in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
+ rc = StorageResultCode.OperationFailedMediaBlank;
+ } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
+ /*
+ * Volume consistency check failed
+ */
+ updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
+ in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
+ rc = StorageResultCode.OperationFailedMediaCorrupt;
+ } else {
+ rc = StorageResultCode.OperationFailedInternalError;
+ }
+
+ /*
+ * Send broadcast intent (if required for the failure)
+ */
+ if (in != null) {
+ mContext.sendBroadcast(in);
+ }
+ }
+
+ return rc;
+ }
+
+ private int doUnmountVolume(String path) {
+ if (getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
+ return VoldResponseCode.OpFailedVolNotMounted;
+ }
+
+ // Notify PackageManager of potential media removal and deal with
+ // return code later on. The caller of this api should be aware or have been
+ // notified that the applications installed on the media will be killed.
+ mPms.updateExternalMediaStatus(false);
+ try {
+ mConnector.doCommand(String.format("volume unmount %s", path));
+ return StorageResultCode.OperationSucceeded;
+ } catch (NativeDaemonConnectorException e) {
+ // Don't worry about mismatch in PackageManager since the
+ // call back will handle the status changes any way.
+ int code = e.getCode();
+ if (code == VoldResponseCode.OpFailedVolNotMounted) {
+ return StorageResultCode.OperationFailedVolumeNotMounted;
+ } else {
+ return StorageResultCode.OperationFailedInternalError;
+ }
+ }
+ }
+
+ private int doFormatVolume(String path) {
+ try {
+ String cmd = String.format("volume format %s", path);
+ mConnector.doCommand(cmd);
+ return StorageResultCode.OperationSucceeded;
+ } catch (NativeDaemonConnectorException e) {
+ int code = e.getCode();
+ if (code == VoldResponseCode.OpFailedNoMedia) {
+ return StorageResultCode.OperationFailedNoMedia;
+ } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
+ return StorageResultCode.OperationFailedMediaCorrupt;
+ } else {
+ return StorageResultCode.OperationFailedInternalError;
+ }
+ }
+ }
+
+ private boolean doGetVolumeShared(String path, String method) {
+ String cmd = String.format("volume shared %s %s", path, method);
+ ArrayList<String> rsp = mConnector.doCommand(cmd);
+
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code;
+ try {
+ code = Integer.parseInt(tok[0]);
+ } catch (NumberFormatException nfe) {
+ Log.e(TAG, String.format("Error parsing code %s", tok[0]));
+ return false;
+ }
+ if (code == VoldResponseCode.ShareEnabledResult) {
+ if (tok[2].equals("enabled"))
+ return true;
+ return false;
+ } else {
+ Log.e(TAG, String.format("Unexpected response code %d", code));
+ return false;
+ }
+ }
+ Log.e(TAG, "Got an empty response");
+ return false;
+ }
+
+ private void notifyShareAvailabilityChange(String method, final boolean avail) {
if (!method.equals("ums")) {
Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
return;
@@ -481,7 +593,7 @@
for (int i = mListeners.size() -1; i >= 0; i--) {
MountServiceBinderListener bl = mListeners.get(i);
try {
- bl.mListener.onShareAvailabilityChanged(method, avail);
+ bl.mListener.onUsbMassStorageConnectionChanged(avail);
} catch (RemoteException rex) {
Log.e(TAG, "Listener dead");
mListeners.remove(i);
@@ -491,22 +603,55 @@
}
}
- Intent intent;
- if (avail) {
- intent = new Intent(Intent.ACTION_UMS_CONNECTED);
- } else {
- intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
+ if (mBooted == true) {
+ Intent intent;
+ if (avail) {
+ intent = new Intent(Intent.ACTION_UMS_CONNECTED);
+ } else {
+ intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
+ }
+ mContext.sendBroadcast(intent);
}
- mContext.sendBroadcast(intent);
}
- void validatePermission(String perm) {
+ private void validatePermission(String perm) {
if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(String.format("Requires %s permission", perm));
}
}
/**
+ * Constructs a new MountService instance
+ *
+ * @param context Binder context for this service
+ */
+ public MountService(Context context) {
+ mContext = context;
+
+ /*
+ * Vold does not run in the simulator, so fake out a mounted
+ * event to trigger MediaScanner
+ */
+ if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+ updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
+ return;
+ }
+
+ // XXX: This will go away soon in favor of IMountServiceObserver
+ mPms = (PackageManagerService) ServiceManager.getService("package");
+
+ mContext.registerReceiver(mBroadcastReceiver,
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+
+ mListeners = new ArrayList<MountServiceBinderListener>();
+
+ mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
+ mReady = false;
+ Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
+ thread.start();
+ }
+
+ /**
* Exposed API calls below here
*/
@@ -549,7 +694,7 @@
* the UMS host could have dirty FAT cache entries
* yet to flush.
*/
- if (unshareVolume(path, "ums") != MountServiceResultCode.OperationSucceeded) {
+ if (setUsbMassStorageEnabled(false) != StorageResultCode.OperationSucceeded) {
Log.e(TAG, "UMS disable on shutdown failed");
}
} else if (state.equals(Environment.MEDIA_CHECKING)) {
@@ -577,75 +722,30 @@
/*
* If the media is mounted, then gracefully unmount it.
*/
- if (unmountVolume(path) != MountServiceResultCode.OperationSucceeded) {
+ if (doUnmountVolume(path) != StorageResultCode.OperationSucceeded) {
Log.e(TAG, "Failed to unmount media for shutdown");
}
}
}
- public String[] getShareMethodList() {
- String[] rdata = new String[1];
- rdata[0] = "ums";
- return rdata;
- }
+ public boolean isUsbMassStorageConnected() {
+ waitForReady();
- public boolean getShareMethodAvailable(String method) {
- ArrayList<String> rsp = mConnector.doCommand("share status " + method);
-
- for (String line : rsp) {
- String []tok = line.split(" ");
- int code;
- try {
- code = Integer.parseInt(tok[0]);
- } catch (NumberFormatException nfe) {
- Log.e(TAG, String.format("Error parsing code %s", tok[0]));
- return false;
- }
- if (code == VoldResponseCode.ShareStatusResult) {
- if (tok[2].equals("available"))
- return true;
- return false;
- } else {
- Log.e(TAG, String.format("Unexpected response code %d", code));
- return false;
- }
+ if (mUmsEnabling) {
+ return true;
}
- Log.e(TAG, "Got an empty response");
- return false;
+ return doGetShareMethodAvailable("ums");
}
- public int shareVolume(String path, String method) {
- return doShareUnshareVolume(path, method, true);
+ public int setUsbMassStorageEnabled(boolean enable) {
+ waitForReady();
+
+ return doShareUnshareVolume(Environment.getExternalStorageDirectory().getPath(), "ums", enable);
}
- public int unshareVolume(String path, String method) {
- return doShareUnshareVolume(path, method, false);
- }
-
- public boolean getVolumeShared(String path, String method) {
- String cmd = String.format("volume shared %s %s", path, method);
- ArrayList<String> rsp = mConnector.doCommand(cmd);
-
- for (String line : rsp) {
- String []tok = line.split(" ");
- int code;
- try {
- code = Integer.parseInt(tok[0]);
- } catch (NumberFormatException nfe) {
- Log.e(TAG, String.format("Error parsing code %s", tok[0]));
- return false;
- }
- if (code == VoldResponseCode.ShareEnabledResult) {
- if (tok[2].equals("enabled"))
- return true;
- return false;
- } else {
- Log.e(TAG, String.format("Unexpected response code %d", code));
- return false;
- }
- }
- Log.e(TAG, "Got an empty response");
- return false;
+ public boolean isUsbMassStorageEnabled() {
+ waitForReady();
+ return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
}
/**
@@ -664,103 +764,38 @@
return mLegacyState;
}
-
- /**
- * Attempt to mount external media
- */
public int mountVolume(String path) {
validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- int rc = MountServiceResultCode.OperationSucceeded;
-
- try {
- mConnector.doCommand(String.format("volume mount %s", path));
- } catch (NativeDaemonConnectorException e) {
- /*
- * Mount failed for some reason
- */
- Intent in = null;
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
- /*
- * Attempt to mount but no media inserted
- */
- rc = MountServiceResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaBlank) {
- /*
- * Media is blank or does not contain a supported filesystem
- */
- updatePublicVolumeState(path, Environment.MEDIA_NOFS);
- in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
- rc = MountServiceResultCode.OperationFailedMediaBlank;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- /*
- * Volume consistency check failed
- */
- updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
- in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
- rc = MountServiceResultCode.OperationFailedMediaCorrupt;
- } else {
- rc = MountServiceResultCode.OperationFailedInternalError;
- }
-
- /*
- * Send broadcast intent (if required for the failure)
- */
- if (in != null) {
- mContext.sendBroadcast(in);
- }
- }
-
- return rc;
+ waitForReady();
+ return doMountVolume(path);
}
- /**
- * Attempt to unmount external media to prepare for eject
- */
public int unmountVolume(String path) {
validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ waitForReady();
- try {
- mConnector.doCommand(String.format("volume unmount %s", path));
- return MountServiceResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedVolNotMounted) {
- return MountServiceResultCode.OperationFailedVolumeNotMounted;
- } else {
- return MountServiceResultCode.OperationFailedInternalError;
- }
- }
+ return doUnmountVolume(path);
}
- /**
- * Synchronously formats a volume
- *
- * @param path The volume path to format
- * @return Error code from MountServiceResultCode
- */
public int formatVolume(String path) {
validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ waitForReady();
- try {
- String cmd = String.format("volume format %s", path);
- mConnector.doCommand(cmd);
- return MountServiceResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
- return MountServiceResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- return MountServiceResultCode.OperationFailedMediaCorrupt;
- } else {
- return MountServiceResultCode.OperationFailedInternalError;
- }
+ return doFormatVolume(path);
+ }
+
+ private void warnOnNotMounted() {
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ Log.w(TAG, "getSecureContainerList() called when storage not mounted");
}
}
public String[] getSecureContainerList() {
validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ waitForReady();
+ warnOnNotMounted();
+
try {
return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
} catch (NativeDaemonConnectorException e) {
@@ -771,82 +806,96 @@
public int createSecureContainer(String id, int sizeMb, String fstype,
String key, int ownerUid) {
validatePermission(android.Manifest.permission.ASEC_CREATE);
+ waitForReady();
+ warnOnNotMounted();
- int rc = MountServiceResultCode.OperationSucceeded;
+ int rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
- rc = MountServiceResultCode.OperationFailedInternalError;
+ rc = StorageResultCode.OperationFailedInternalError;
}
return rc;
}
public int finalizeSecureContainer(String id) {
validatePermission(android.Manifest.permission.ASEC_CREATE);
+ warnOnNotMounted();
- int rc = MountServiceResultCode.OperationSucceeded;
+ int rc = StorageResultCode.OperationSucceeded;
try {
mConnector.doCommand(String.format("asec finalize %s", id));
} catch (NativeDaemonConnectorException e) {
- rc = MountServiceResultCode.OperationFailedInternalError;
+ rc = StorageResultCode.OperationFailedInternalError;
}
return rc;
}
public int destroySecureContainer(String id) {
validatePermission(android.Manifest.permission.ASEC_DESTROY);
+ waitForReady();
+ warnOnNotMounted();
- int rc = MountServiceResultCode.OperationSucceeded;
+ int rc = StorageResultCode.OperationSucceeded;
try {
mConnector.doCommand(String.format("asec destroy %s", id));
} catch (NativeDaemonConnectorException e) {
- rc = MountServiceResultCode.OperationFailedInternalError;
+ rc = StorageResultCode.OperationFailedInternalError;
}
return rc;
}
public int mountSecureContainer(String id, String key, int ownerUid) {
validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+ waitForReady();
+ warnOnNotMounted();
- int rc = MountServiceResultCode.OperationSucceeded;
+ int rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
- rc = MountServiceResultCode.OperationFailedInternalError;
+ rc = StorageResultCode.OperationFailedInternalError;
}
return rc;
}
public int unmountSecureContainer(String id) {
validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+ waitForReady();
+ warnOnNotMounted();
- int rc = MountServiceResultCode.OperationSucceeded;
+ int rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("asec unmount %s", id);
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
- rc = MountServiceResultCode.OperationFailedInternalError;
+ rc = StorageResultCode.OperationFailedInternalError;
}
return rc;
}
public int renameSecureContainer(String oldId, String newId) {
validatePermission(android.Manifest.permission.ASEC_RENAME);
+ waitForReady();
+ warnOnNotMounted();
- int rc = MountServiceResultCode.OperationSucceeded;
+ int rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("asec rename %s %s", oldId, newId);
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
- rc = MountServiceResultCode.OperationFailedInternalError;
+ rc = StorageResultCode.OperationFailedInternalError;
}
return rc;
}
public String getSecureContainerPath(String id) {
validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ waitForReady();
+ warnOnNotMounted();
+
ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
for (String line : rsp) {
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 016aa52..cf083c04 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -21,7 +21,6 @@
import android.os.Environment;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
@@ -41,6 +40,7 @@
* protocol.
*/
final class NativeDaemonConnector implements Runnable {
+ private static final boolean LOCAL_LOGD = false;
private BlockingQueue<String> mResponseQueue;
private OutputStream mOutputStream;
@@ -110,7 +110,7 @@
for (int i = 0; i < count; i++) {
if (buffer[i] == 0) {
String event = new String(buffer, start, i - start);
-// Log.d(TAG, "Got packet {" + event + "}");
+ if (LOCAL_LOGD) Log.d(TAG, String.format("RCV <- {%s}", event));
String[] tokens = event.split(" ");
try {
@@ -177,7 +177,7 @@
*/
private void sendCommand(String command, String argument) {
synchronized (this) {
- Log.d(TAG, "sendCommand {" + command + "} {" + argument + "}");
+ if (LOCAL_LOGD) Log.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
if (mOutputStream == null) {
Log.e(TAG, "No connection to daemon", new IllegalStateException());
} else {
@@ -210,7 +210,7 @@
while (!complete) {
try {
String line = mResponseQueue.take();
- Log.d(TAG, String.format("RSP -> {%s}", line));
+ if (LOCAL_LOGD) Log.d(TAG, String.format("RSP <- {%s}", line));
String[] tokens = line.split(" ");
try {
code = Integer.parseInt(tokens[0]);
@@ -219,19 +219,23 @@
String.format("Invalid response from daemon (%s)", line));
}
- if ((code >= 200) && (code < 600))
+ if ((code >= 200) && (code < 600)) {
complete = true;
+ }
response.add(line);
} catch (InterruptedException ex) {
- Log.e(TAG, "InterruptedException");
+ Log.e(TAG, "Failed to process response", ex);
}
}
if (code >= ResponseCode.FailedRangeStart &&
code <= ResponseCode.FailedRangeEnd) {
- throw new NativeDaemonConnectorException(code, String.format(
- "Command %s failed with code %d",
- cmd, code));
+ /*
+ * Note: The format of the last response in this case is
+ * "NNN <errmsg>"
+ */
+ throw new NativeDaemonConnectorException(
+ code, cmd, response.get(response.size()-1).substring(4));
}
return response;
}
@@ -246,13 +250,18 @@
String[] rdata = new String[rsp.size()-1];
int idx = 0;
- for (String line : rsp) {
+ for (int i = 0; i < rsp.size(); i++) {
+ String line = rsp.get(i);
try {
String[] tok = line.split(" ");
int code = Integer.parseInt(tok[0]);
if (code == expectedResponseCode) {
rdata[idx++] = line.substring(tok[0].length() + 1);
} else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
+ if (LOCAL_LOGD) Log.d(TAG, String.format("List terminated with {%s}", line));
+ if (i != rsp.size()) {
+ Log.w(TAG, String.format("Recv'd %d lines after list term", (rsp.size()-i)));
+ }
return rdata;
} else {
throw new NativeDaemonConnectorException(
diff --git a/services/java/com/android/server/NativeDaemonConnectorException.java b/services/java/com/android/server/NativeDaemonConnectorException.java
index e60aaf8..426742b 100644
--- a/services/java/com/android/server/NativeDaemonConnectorException.java
+++ b/services/java/com/android/server/NativeDaemonConnectorException.java
@@ -22,6 +22,7 @@
public class NativeDaemonConnectorException extends RuntimeException
{
private int mCode = -1;
+ private String mCmd;
public NativeDaemonConnectorException() {}
@@ -30,13 +31,18 @@
super(error);
}
- public NativeDaemonConnectorException(int code, String error)
+ public NativeDaemonConnectorException(int code, String cmd, String error)
{
- super(error);
+ super(String.format("Cmd {%s} failed with code %d : {%s}", cmd, code, error));
mCode = code;
+ mCmd = cmd;
}
public int getCode() {
return mCode;
}
+
+ public String getCmd() {
+ return mCmd;
+ }
}
diff --git a/services/java/com/android/server/NetStatService.java b/services/java/com/android/server/NetStatService.java
index 0834405..ac3445e 100644
--- a/services/java/com/android/server/NetStatService.java
+++ b/services/java/com/android/server/NetStatService.java
@@ -27,11 +27,11 @@
}
public long getMobileTxPackets() {
- return TrafficStats.getMobileTxPkts();
+ return TrafficStats.getMobileTxPackets();
}
public long getMobileRxPackets() {
- return TrafficStats.getMobileRxPkts();
+ return TrafficStats.getMobileRxPackets();
}
public long getMobileTxBytes() {
@@ -43,11 +43,11 @@
}
public long getTotalTxPackets() {
- return TrafficStats.getTotalTxPkts();
+ return TrafficStats.getTotalTxPackets();
}
public long getTotalRxPackets() {
- return TrafficStats.getTotalRxPkts();
+ return TrafficStats.getTotalRxPackets();
}
public long getTotalTxBytes() {
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index b34b50a..d41aacf 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -334,9 +334,9 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
try {
- String cmd = "tether dns set ";
+ String cmd = "tether dns set";
for (String s : dns) {
- cmd += InetAddress.getByName(s).toString() + " ";
+ cmd += " " + InetAddress.getByName(s).getHostAddress();
}
mConnector.doCommand(cmd);
} catch (UnknownHostException e) {
@@ -373,14 +373,16 @@
return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult);
}
- public void attachPppd(String tty, String localAddr, String remoteAddr)
- throws IllegalStateException {
+ public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr,
+ String dns2Addr) throws IllegalStateException {
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
- mConnector.doCommand(String.format("pppd attach %s %s %s", tty,
- InetAddress.getByName(localAddr).toString(),
- InetAddress.getByName(localAddr).toString()));
+ mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty,
+ InetAddress.getByName(localAddr).getHostAddress(),
+ InetAddress.getByName(remoteAddr).getHostAddress(),
+ InetAddress.getByName(dns1Addr).getHostAddress(),
+ InetAddress.getByName(dns2Addr).getHostAddress()));
} catch (UnknownHostException e) {
throw new IllegalStateException("Error resolving addr", e);
}
@@ -392,4 +394,3 @@
mConnector.doCommand(String.format("pppd detach %s", tty));
}
}
-
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 02a0401..ad8ab84 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -50,6 +50,7 @@
import android.os.Power;
import android.os.Process;
import android.os.RemoteException;
+import android.os.storage.StorageManager;
import android.os.SystemProperties;
import android.os.Vibrator;
import android.provider.Settings;
@@ -330,9 +331,9 @@
updateAdbNotification();
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
- || action.equals(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE)) {
+ || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
String pkgList[] = null;
- if (action.equals(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE)) {
+ if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else {
Uri uri = intent.getData();
@@ -406,6 +407,10 @@
mSound.setUsesWakeLock(context);
mToastQueue = new ArrayList<ToastRecord>();
mHandler = new WorkerHandler();
+
+ StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ sm.registerListener(new com.android.internal.app.StorageNotification(context));
+
mStatusBarService = statusBar;
statusBar.setNotificationCallbacks(mNotificationCallbacks);
@@ -440,7 +445,7 @@
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mIntentReceiver, filter);
- IntentFilter sdFilter = new IntentFilter(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mIntentReceiver, sdFilter);
SettingsObserver observer = new SettingsObserver(mHandler);
diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java
index dbd1826..9551db5 100644
--- a/services/java/com/android/server/PackageManagerBackupAgent.java
+++ b/services/java/com/android/server/PackageManagerBackupAgent.java
@@ -40,6 +40,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* We back up the signatures of each package so that during a system restore,
@@ -100,6 +101,20 @@
return mRestoredSignatures.get(packageName);
}
+
+ public Set<String> getRestoredPackages() {
+ if (mRestoredSignatures == null) {
+ Log.w(TAG, "getRestoredPackages() before metadata read!");
+ return null;
+ }
+
+ // This is technically the set of packages on the originating handset
+ // that had backup agents at all, not limited to the set of packages
+ // that had actually contributed a restore dataset, but it's a
+ // close enough approximation for our purposes and does not require any
+ // additional involvement by the transport to obtain.
+ return mRestoredSignatures.keySet();
+ }
// The backed up data is the signature block for each app, keyed by
// the package name.
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 238403c..c99480f 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -74,7 +74,7 @@
import android.os.FileObserver;
import android.os.FileUtils;
import android.os.Handler;
-import android.os.MountServiceResultCode;
+import android.os.storage.StorageResultCode;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ServiceManager;
@@ -96,11 +96,13 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@@ -117,6 +119,7 @@
private static final String TAG = "PackageManager";
private static final boolean DEBUG_SETTINGS = false;
private static final boolean DEBUG_PREFERRED = false;
+ private static final boolean DEBUG_UPGRADE = false;
private static final boolean MULTIPLE_APPLICATION_UIDS = true;
private static final int RADIO_UID = Process.PHONE_UID;
@@ -221,7 +224,6 @@
final Settings mSettings;
boolean mRestoredSettings;
- boolean mReportedUidError;
// Group-ids that are given to all packages as read from etc/permissions/*.xml.
int[] mGlobalGids;
@@ -271,6 +273,10 @@
final HashMap<String, PackageParser.PermissionGroup> mPermissionGroups =
new HashMap<String, PackageParser.PermissionGroup>();
+ // Packages whose data we have transfered into another package, thus
+ // should no longer exist.
+ final HashSet<String> mTransferedPackages = new HashSet<String>();
+
// Broadcast actions that are only available to the system.
final HashSet<String> mProtectedBroadcasts = new HashSet<String>();
@@ -661,16 +667,43 @@
}
}
+ // Find base frameworks (resource packages without code).
mFrameworkInstallObserver = new AppDirObserver(
mFrameworkDir.getPath(), OBSERVER_EVENTS, true);
mFrameworkInstallObserver.startWatching();
scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM,
scanMode | SCAN_NO_DEX);
+
+ // Collect all system packages.
mSystemAppDir = new File(Environment.getRootDirectory(), "app");
mSystemInstallObserver = new AppDirObserver(
mSystemAppDir.getPath(), OBSERVER_EVENTS, true);
mSystemInstallObserver.startWatching();
scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM, scanMode);
+
+ if (mInstaller != null) {
+ if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
+ mInstaller.moveFiles();
+ }
+
+ // Prune any system packages that no longer exist.
+ Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
+ while (psit.hasNext()) {
+ PackageSetting ps = psit.next();
+ if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0
+ && !mPackages.containsKey(ps.name)) {
+ psit.remove();
+ String msg = "System package " + ps.name
+ + " no longer exists; wiping its data";
+ reportSettingsProblem(Log.WARN, msg);
+ if (mInstaller != null) {
+ // XXX how to set useEncryptedFSDir for packages that
+ // are not encrypted?
+ mInstaller.remove(ps.name, true);
+ }
+ }
+ }
+
mAppInstallDir = new File(dataDir, "app");
if (mInstaller == null) {
// Make sure these dirs exist, when we are running in
@@ -2078,17 +2111,30 @@
File file = new File(dir, files[i]);
PackageParser.Package pkg = scanPackageLI(file,
flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
+ // Don't mess around with apps in system partition.
+ if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0) {
+ // Delete the apk
+ Log.w(TAG, "Cleaning up failed install of " + file);
+ file.delete();
+ }
}
}
+ private static File getSettingsProblemFile() {
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File fname = new File(systemDir, "uiderrors.txt");
+ return fname;
+ }
+
private static void reportSettingsProblem(int priority, String msg) {
try {
- File dataDir = Environment.getDataDirectory();
- File systemDir = new File(dataDir, "system");
- File fname = new File(systemDir, "uiderrors.txt");
+ File fname = getSettingsProblemFile();
FileOutputStream out = new FileOutputStream(fname, true);
PrintWriter pw = new PrintWriter(out);
- pw.println(msg);
+ SimpleDateFormat formatter = new SimpleDateFormat();
+ String dateString = formatter.format(new Date(System.currentTimeMillis()));
+ pw.println(dateString + ": " + msg);
pw.close();
FileUtils.setPermissions(
fname.toString(),
@@ -2295,9 +2341,31 @@
((pkg.applicationInfo.flags & ApplicationInfo.FLAG_NEVER_ENCRYPT) == 0);
}
+ private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) {
+ if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+ Log.w(TAG, "Unable to update from " + oldPkg.name
+ + " to " + newPkg.packageName
+ + ": old package not in system partition");
+ return false;
+ } else if (mPackages.get(oldPkg.name) != null) {
+ Log.w(TAG, "Unable to update from " + oldPkg.name
+ + " to " + newPkg.packageName
+ + ": old package still exists");
+ return false;
+ }
+ return true;
+ }
+
private PackageParser.Package scanPackageLI(
PackageParser.Package pkg, int parseFlags, int scanMode) {
File scanFile = new File(pkg.mScanPath);
+ if (scanFile == null || pkg.applicationInfo.sourceDir == null ||
+ pkg.applicationInfo.publicSourceDir == null) {
+ // Bail out. The resource and code paths haven't been set.
+ Log.w(TAG, " Code and resource paths haven't been set correctly");
+ mLastScanError = PackageManager.INSTALL_FAILED_INVALID_APK;
+ return null;
+ }
mScanningPath = scanFile;
if (pkg == null) {
mLastScanError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
@@ -2439,22 +2507,67 @@
}
}
+ // Check if we are renaming from an original package name.
+ PackageSetting origPackage = null;
+ if (pkg.mOriginalPackage != null) {
+ // We will only retrieve the setting for it if it already
+ // exists; otherwise we need to make a new one later.
+ origPackage = mSettings.peekPackageLP(pkg.mOriginalPackage);
+ if (origPackage != null) {
+ if (!verifyPackageUpdate(origPackage, pkg)) {
+ origPackage = null;
+ } else if (origPackage.sharedUser != null) {
+ if (!origPackage.sharedUser.name.equals(pkg.mSharedUserId)) {
+ Log.w(TAG, "Unable to migrate data from " + origPackage.name
+ + " to " + pkg.packageName + ": old uid "
+ + origPackage.sharedUser.name
+ + " differs from " + pkg.mSharedUserId);
+ origPackage = null;
+ }
+ } else {
+ if (DEBUG_UPGRADE) Log.v(TAG, "Migrating data from "
+ + origPackage.name + " to " + pkg.packageName);
+ }
+ }
+ }
+
+ if (mTransferedPackages.contains(pkg.packageName)) {
+ Log.w(TAG, "Package " + pkg.packageName
+ + " was transferred to another, but its .apk remains");
+ }
+
// Just create the setting, don't add it yet. For already existing packages
// the PkgSetting exists already and doesn't have to be created.
- pkgSetting = mSettings.getPackageLP(pkg, suid, destCodeFile,
+ pkgSetting = mSettings.getPackageLP(pkg, origPackage, suid, destCodeFile,
destResourceFile, pkg.applicationInfo.flags, true, false);
if (pkgSetting == null) {
Log.w(TAG, "Creating application package " + pkgName + " failed");
mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
return null;
}
- if(mSettings.mDisabledSysPackages.get(pkg.packageName) != null) {
+ if (mSettings.mDisabledSysPackages.get(pkg.packageName) != null) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
}
pkg.applicationInfo.uid = pkgSetting.userId;
pkg.mExtras = pkgSetting;
+ if (pkg.mAdoptPermissions != null) {
+ // This package wants to adopt ownership of permissions from
+ // another package.
+ for (int i=pkg.mAdoptPermissions.size()-1; i>=0; i--) {
+ String origName = pkg.mAdoptPermissions.get(i);
+ PackageSetting orig = mSettings.peekPackageLP(origName);
+ if (orig != null) {
+ if (verifyPackageUpdate(orig, pkg)) {
+ if (DEBUG_UPGRADE) Log.v(TAG, "Adopting permissions from "
+ + origName + " to " + pkg.packageName);
+ mSettings.transferPermissions(origName, pkg.packageName);
+ }
+ }
+ }
+ }
+
if (!verifySignaturesLP(pkgSetting, pkg, parseFlags,
(scanMode&SCAN_UPDATE_SIGNATURE) != 0)) {
if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) == 0) {
@@ -2543,6 +2656,9 @@
} else {
dataPath = new File(mAppDataDir, pkgName);
}
+
+ boolean uidError = false;
+
if (dataPath.exists()) {
mOutPermissions[1] = 0;
FileUtils.getPermissions(dataPath.getPath(), mOutPermissions);
@@ -2556,7 +2672,7 @@
// current data so the application will still work.
if (mInstaller != null) {
int ret = mInstaller.remove(pkgName, useEncryptedFSDir);
- if(ret >= 0) {
+ if (ret >= 0) {
// Old data gone!
String msg = "System package " + pkg.packageName
+ " has changed from uid: "
@@ -2591,12 +2707,12 @@
+ mOutPermissions[1] + " on disk, "
+ pkg.applicationInfo.uid + " in settings";
synchronized (mPackages) {
- if (!mReportedUidError) {
- mReportedUidError = true;
- msg = msg + "; read messages:\n"
- + mSettings.getReadMessagesLP();
+ mSettings.mReadMessages.append(msg);
+ mSettings.mReadMessages.append('\n');
+ uidError = true;
+ if (!pkgSetting.uidError) {
+ reportSettingsProblem(Log.ERROR, msg);
}
- reportSettingsProblem(Log.ERROR, msg);
}
}
}
@@ -2604,6 +2720,29 @@
} else {
if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGV)
Log.v(TAG, "Want this data dir: " + dataPath);
+ if (pkgSetting.origPackage != null) {
+ synchronized (mPackages) {
+ // This package is being update from another; rename the
+ // old one's data dir.
+ String msg = "Transfering data from old package "
+ + pkgSetting.origPackage.name + " to new package "
+ + pkgSetting.name;
+ reportSettingsProblem(Log.WARN, msg);
+ if (mInstaller != null) {
+ int ret = mInstaller.rename(pkgSetting.origPackage.name,
+ pkgName, useEncryptedFSDir);
+ if(ret < 0) {
+ msg = "Error transfering data from old package "
+ + pkgSetting.origPackage.name + " to new package "
+ + pkgSetting.name;
+ reportSettingsProblem(Log.WARN, msg);
+ }
+ }
+ // And now uninstall the old package.
+ mInstaller.remove(pkgSetting.origPackage.name, useEncryptedFSDir);
+ mSettings.removePackageLP(pkgSetting.origPackage.name);
+ }
+ }
//invoke installer to do the actual installation
if (mInstaller != null) {
int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid,
@@ -2629,8 +2768,16 @@
pkg.applicationInfo.dataDir = null;
}
}
+
+ pkgSetting.uidError = uidError;
}
+ // No longer need to retain this.
+ if (pkgSetting.origPackage != null) {
+ mTransferedPackages.add(pkgSetting.origPackage.name);
+ pkgSetting.origPackage = null;
+ }
+
// Perform shared library installation and dex validation and
// optimization, if this is not a system app.
if (mInstaller != null) {
@@ -4825,69 +4972,82 @@
File tmpPackageFile = new File(args.getCodePath());
boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
boolean onSd = ((pFlags & PackageManager.INSTALL_ON_SDCARD) != 0);
- boolean replacingExistingPackage = false;
+ boolean replace = false;
int scanMode = SCAN_MONITOR | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE
| (newInstall ? SCAN_NEW_INSTALL : 0);
// Result object to be returned
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
- main_flow: try {
- // Retrieve PackageSettings and parse package
- int parseFlags = PackageParser.PARSE_CHATTY |
- (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) |
- (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
- parseFlags |= mDefParseFlags;
- PackageParser pp = new PackageParser(tmpPackageFile.getPath());
- pp.setSeparateProcesses(mSeparateProcesses);
- final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
- null, mMetrics, parseFlags);
- if (pkg == null) {
- res.returnCode = pp.getParseError();
- break main_flow;
+ // Retrieve PackageSettings and parse package
+ int parseFlags = PackageParser.PARSE_CHATTY |
+ (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) |
+ (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
+ parseFlags |= mDefParseFlags;
+ PackageParser pp = new PackageParser(tmpPackageFile.getPath());
+ pp.setSeparateProcesses(mSeparateProcesses);
+ final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
+ null, mMetrics, parseFlags);
+ if (pkg == null) {
+ res.returnCode = pp.getParseError();
+ return;
+ }
+ String pkgName = res.name = pkg.packageName;
+ if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
+ if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
+ res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
+ return;
}
- String pkgName = res.name = pkg.packageName;
- if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
- if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
- res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
- break main_flow;
+ }
+ if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
+ res.returnCode = pp.getParseError();
+ return;
+ }
+ // Some preinstall checks
+ if (forwardLocked && onSd) {
+ // Make sure forward locked apps can only be installed
+ // on internal storage
+ Log.w(TAG, "Cannot install protected apps on sdcard");
+ res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ return;
+ }
+ // Get rid of all references to package scan path via parser.
+ pp = null;
+ String oldCodePath = null;
+ boolean systemApp = false;
+ synchronized (mPackages) {
+ // Check if installing already existing package
+ if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0
+ && mPackages.containsKey(pkgName)) {
+ replace = true;
+ }
+ PackageSetting ps = mSettings.mPackages.get(pkgName);
+ if (ps != null) {
+ oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
+ if (ps.pkg != null && ps.pkg.applicationInfo != null) {
+ systemApp = (ps.pkg.applicationInfo.flags &
+ ApplicationInfo.FLAG_SYSTEM) != 0;
}
}
- if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
- res.returnCode = pp.getParseError();
- break main_flow;
- }
+ }
- // Get rid of all references to package scan path via parser.
- pp = null;
- String oldCodePath = null;
- synchronized (mPackages) {
- //check if installing already existing package
- if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0
- && mPackages.containsKey(pkgName)) {
- replacingExistingPackage = true;
- }
- PackageSetting ps = mSettings.mPackages.get(pkgName);
- if (ps != null) {
- oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
- }
- }
-
- if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
- res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- break main_flow;
- }
- // Set application objects path explicitly after the rename
- setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
- if(replacingExistingPackage) {
- replacePackageLI(pkg, parseFlags, scanMode,
- installerPackageName, res);
- } else {
- installNewPackageLI(pkg, parseFlags, scanMode,
- installerPackageName,res);
- }
- } finally {
- if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- }
+ if (systemApp && onSd) {
+ // Disable updates to system apps on sdcard
+ Log.w(TAG, "Cannot install updates to system apps on sdcard");
+ res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ return;
+ }
+ if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
+ res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ return;
+ }
+ // Set application objects path explicitly after the rename
+ setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
+ if(replace) {
+ replacePackageLI(pkg, parseFlags, scanMode,
+ installerPackageName, res);
+ } else {
+ installNewPackageLI(pkg, parseFlags, scanMode,
+ installerPackageName,res);
}
}
@@ -5951,16 +6111,31 @@
}
}
}
+
pw.println(" ");
pw.println("Settings parse messages:");
pw.println(mSettings.mReadMessages.toString());
+
+ pw.println(" ");
+ pw.println("Package warning messages:");
+ File fname = getSettingsProblemFile();
+ FileInputStream in;
+ try {
+ in = new FileInputStream(fname);
+ int avail = in.available();
+ byte[] data = new byte[avail];
+ in.read(data);
+ pw.println(new String(data));
+ } catch (FileNotFoundException e) {
+ } catch (IOException e) {
+ }
}
synchronized (mProviders) {
pw.println(" ");
pw.println("Registered ContentProviders:");
for (PackageParser.Provider p : mProviders.values()) {
- pw.println(" ["); pw.println(p.info.authority); pw.println("]: ");
+ pw.print(" ["); pw.print(p.info.authority); pw.print("]: ");
pw.println(p.toString());
}
}
@@ -5972,7 +6147,7 @@
final static int TYPE_DYNAMIC = 2;
final String name;
- final String sourcePackage;
+ String sourcePackage;
final int type;
PackageParser.Permission perm;
PermissionInfo pendingInfo;
@@ -6437,6 +6612,8 @@
private String timeStampString = "0";
int versionCode;
+ boolean uidError;
+
PackageSignatures signatures = new PackageSignatures();
boolean permissionsFixed;
@@ -6448,6 +6625,8 @@
int enabled = COMPONENT_ENABLED_STATE_DEFAULT;
int installStatus = PKG_INSTALL_COMPLETE;
+ PackageSettingBase origPackage;
+
/* package name of the app that installed this package */
String installerPackageName;
@@ -6455,13 +6634,17 @@
int pVersionCode, int pkgFlags) {
super(pkgFlags);
this.name = name;
+ init(codePath, resourcePath, pVersionCode);
+ }
+
+ void init(File codePath, File resourcePath, int pVersionCode) {
this.codePath = codePath;
this.codePathString = codePath.toString();
this.resourcePath = resourcePath;
this.resourcePathString = resourcePath.toString();
this.versionCode = pVersionCode;
}
-
+
public void setInstallerPackageName(String packageName) {
installerPackageName = packageName;
}
@@ -6667,11 +6850,11 @@
mBackupSettingsFilename = new File(systemDir, "packages-backup.xml");
}
- PackageSetting getPackageLP(PackageParser.Package pkg,
+ PackageSetting getPackageLP(PackageParser.Package pkg, PackageSetting origPackage,
SharedUserSetting sharedUser, File codePath, File resourcePath,
int pkgFlags, boolean create, boolean add) {
final String name = pkg.packageName;
- PackageSetting p = getPackageLP(name, sharedUser, codePath,
+ PackageSetting p = getPackageLP(name, origPackage, sharedUser, codePath,
resourcePath, pkg.mVersionCode, pkgFlags, create, add);
return p;
}
@@ -6813,7 +6996,31 @@
return null;
}
- private PackageSetting getPackageLP(String name,
+ // Transfer ownership of permissions from one package to another.
+ private void transferPermissions(String origPkg, String newPkg) {
+ // Transfer ownership of permissions to the new package.
+ for (int i=0; i<2; i++) {
+ HashMap<String, BasePermission> permissions =
+ i == 0 ? mPermissionTrees : mPermissions;
+ for (BasePermission bp : permissions.values()) {
+ if (origPkg.equals(bp.sourcePackage)) {
+ if (DEBUG_UPGRADE) Log.v(TAG,
+ "Moving permission " + bp.name
+ + " from pkg " + bp.sourcePackage
+ + " to " + newPkg);
+ bp.sourcePackage = newPkg;
+ bp.perm = null;
+ if (bp.pendingInfo != null) {
+ bp.sourcePackage = newPkg;
+ }
+ bp.uid = 0;
+ bp.gids = null;
+ }
+ }
+ }
+ }
+
+ private PackageSetting getPackageLP(String name, PackageSetting origPackage,
SharedUserSetting sharedUser, File codePath, File resourcePath,
int vc, int pkgFlags, boolean create, boolean add) {
PackageSetting p = mPackages.get(name);
@@ -6857,36 +7064,49 @@
return null;
}
p = new PackageSetting(name, codePath, resourcePath, vc, pkgFlags);
- p.setTimeStamp(codePath.lastModified());
- p.sharedUser = sharedUser;
- if (sharedUser != null) {
- p.userId = sharedUser.userId;
- } else if (MULTIPLE_APPLICATION_UIDS) {
- // Clone the setting here for disabled system packages
- PackageSetting dis = mDisabledSysPackages.get(name);
- if (dis != null) {
- // For disabled packages a new setting is created
- // from the existing user id. This still has to be
- // added to list of user id's
- // Copy signatures from previous setting
- if (dis.signatures.mSignatures != null) {
- p.signatures.mSignatures = dis.signatures.mSignatures.clone();
- }
- p.userId = dis.userId;
- // Clone permissions
- p.grantedPermissions = new HashSet<String>(dis.grantedPermissions);
- p.loadedPermissions = new HashSet<String>(dis.loadedPermissions);
- // Clone component info
- p.disabledComponents = new HashSet<String>(dis.disabledComponents);
- p.enabledComponents = new HashSet<String>(dis.enabledComponents);
- // Add new setting to list of user ids
- addUserIdLP(p.userId, p, name);
- } else {
- // Assign new user id
- p.userId = newUserIdLP(p);
- }
+ if (origPackage != null) {
+ // We are consuming the data from an existing package.
+ if (DEBUG_UPGRADE) Log.v(TAG, "Package " + name
+ + " is adopting original package " + origPackage.name);
+ p.copyFrom(origPackage);
+ p.sharedUser = origPackage.sharedUser;
+ p.userId = origPackage.userId;
+ p.origPackage = origPackage;
+ transferPermissions(origPackage.name, name);
+ // Update new package state.
+ p.setTimeStamp(codePath.lastModified());
} else {
- p.userId = FIRST_APPLICATION_UID;
+ p.setTimeStamp(codePath.lastModified());
+ p.sharedUser = sharedUser;
+ if (sharedUser != null) {
+ p.userId = sharedUser.userId;
+ } else if (MULTIPLE_APPLICATION_UIDS) {
+ // Clone the setting here for disabled system packages
+ PackageSetting dis = mDisabledSysPackages.get(name);
+ if (dis != null) {
+ // For disabled packages a new setting is created
+ // from the existing user id. This still has to be
+ // added to list of user id's
+ // Copy signatures from previous setting
+ if (dis.signatures.mSignatures != null) {
+ p.signatures.mSignatures = dis.signatures.mSignatures.clone();
+ }
+ p.userId = dis.userId;
+ // Clone permissions
+ p.grantedPermissions = new HashSet<String>(dis.grantedPermissions);
+ p.loadedPermissions = new HashSet<String>(dis.loadedPermissions);
+ // Clone component info
+ p.disabledComponents = new HashSet<String>(dis.disabledComponents);
+ p.enabledComponents = new HashSet<String>(dis.enabledComponents);
+ // Add new setting to list of user ids
+ addUserIdLP(p.userId, p, name);
+ } else {
+ // Assign new user id
+ p.userId = newUserIdLP(p);
+ }
+ } else {
+ p.userId = FIRST_APPLICATION_UID;
+ }
}
if (p.userId < 0) {
reportSettingsProblem(Log.WARN,
@@ -7248,6 +7468,9 @@
serializer.attribute(null, "sharedUserId",
Integer.toString(pkg.userId));
}
+ if (pkg.uidError) {
+ serializer.attribute(null, "uidError", "true");
+ }
if (pkg.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
serializer.attribute(null, "enabled",
pkg.enabled == COMPONENT_ENABLED_STATE_ENABLED
@@ -7441,7 +7664,7 @@
final PendingPackage pp = mPendingPackages.get(i);
Object idObj = getUserIdLP(pp.sharedId);
if (idObj != null && idObj instanceof SharedUserSetting) {
- PackageSetting p = getPackageLP(pp.name,
+ PackageSetting p = getPackageLP(pp.name, null,
(SharedUserSetting)idObj, pp.codePath, pp.resourcePath,
pp.versionCode, pp.pkgFlags, true, true);
if (p == null) {
@@ -7610,6 +7833,7 @@
String resourcePathStr = null;
String systemStr = null;
String installerPackageName = null;
+ String uidError = null;
int pkgFlags = 0;
String timeStampStr;
long timeStamp = 0;
@@ -7619,6 +7843,7 @@
try {
name = parser.getAttributeValue(null, "name");
idStr = parser.getAttributeValue(null, "userId");
+ uidError = parser.getAttributeValue(null, "uidError");
sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
codePathStr = parser.getAttributeValue(null, "codePath");
resourcePathStr = parser.getAttributeValue(null, "resourcePath");
@@ -7712,6 +7937,7 @@
+ parser.getPositionDescription());
}
if (packageSetting != null) {
+ packageSetting.uidError = "true".equals(uidError);
packageSetting.installerPackageName = installerPackageName;
final String enabledStr = parser.getAttributeValue(null, "enabled");
if (enabledStr != null) {
@@ -8069,17 +8295,17 @@
int rc = mountService.createSecureContainer(
pkgName, mbLen, "vfat", sdEncKey, Process.SYSTEM_UID);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format("Failed to create container (%d)", rc));
rc = mountService.destroySecureContainer(pkgName);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format("Failed to cleanup container (%d)", rc));
return null;
}
rc = mountService.createSecureContainer(
pkgName, mbLen, "vfat", sdEncKey, Process.SYSTEM_UID);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, String.format("Failed to create container (2nd try) (%d)", rc));
return null;
}
@@ -8099,7 +8325,7 @@
int rc = getMountService().mountSecureContainer(pkgName, sdEncKey, ownerUid);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.i(TAG, "Failed to mount container for pkg : " + pkgName + " rc : " + rc);
return null;
}
@@ -8110,7 +8336,7 @@
private boolean unMountSdDir(String pkgName) {
// STOPSHIP unmount directory
int rc = getMountService().unmountSecureContainer(pkgName);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, "Failed to unmount : " + pkgName + " with rc " + rc);
return false;
}
@@ -8134,7 +8360,7 @@
private boolean finalizeSdDir(String pkgName) {
int rc = getMountService().finalizeSecureContainer(pkgName);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.i(TAG, "Failed to finalize container for pkg : " + pkgName);
return false;
}
@@ -8143,7 +8369,7 @@
private boolean destroySdDir(String pkgName) {
int rc = getMountService().destroySecureContainer(pkgName);
- if (rc != MountServiceResultCode.OperationSucceeded) {
+ if (rc != StorageResultCode.OperationSucceeded) {
Log.i(TAG, "Failed to destroy container for pkg : " + pkgName);
return false;
}
@@ -8209,8 +8435,8 @@
}
public void updateExternalMediaStatus(final boolean mediaStatus) {
- final boolean DEBUG = true;
- if (DEBUG) Log.i(TAG, "updateExterMediaStatus::");
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" +
+ mediaStatus+", mMediaMounted=" + mMediaMounted);
if (mediaStatus == mMediaMounted) {
return;
}
@@ -8232,9 +8458,6 @@
HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>();
int uidList[] = new int[list.length];
int num = 0;
- for (int i = 0; i < uidList.length; i++) {
- uidList[i] = Process.LAST_APPLICATION_UID;
- }
synchronized (mPackages) {
Set<String> appList = mSettings.findPackagesWithFlag(ApplicationInfo.FLAG_ON_SDCARD);
for (String cid : list) {
@@ -8256,24 +8479,34 @@
processCids.put(args, ps.codePathString);
int uid = ps.userId;
if (uid != -1) {
- int idx = Arrays.binarySearch(uidList, uid);
- if (idx < 0) {
- uidList[-idx] = uid;
- num++;
- }
+ uidList[num++] = uid;
}
}
}
- int uidArr[] = uidList;
- if ((num > 0) && (num < uidList.length)) {
+ int uidArr[] = null;
+ if (num > 0) {
+ // Sort uid list
+ Arrays.sort(uidList, 0, num);
+ // Throw away duplicates
uidArr = new int[num];
- for (int i = 0; i < num; i++) {
- uidArr[i] = uidList[i];
+ uidArr[0] = uidList[0];
+ int di = 0;
+ for (int i = 1; i < num; i++) {
+ if (uidList[i-1] != uidList[i]) {
+ uidArr[di++] = uidList[i];
+ }
+ }
+ if (true) {
+ for (int j = 0; j < num; j++) {
+ Log.i(TAG, "uidArr[" + j + "]=" + uidArr[j]);
+ }
}
}
if (mediaStatus) {
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages");
loadMediaPackages(processCids, uidArr);
} else {
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages");
unloadMediaPackages(processCids, uidArr);
}
}
@@ -8286,9 +8519,11 @@
Bundle extras = new Bundle();
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
pkgList.toArray(new String[size]));
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
- String action = mediaStatus ? Intent.ACTION_MEDIA_RESOURCES_AVAILABLE
- : Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE;
+ if (uidArr != null) {
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
+ }
+ String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
+ : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
sendPackageBroadcast(action, null, extras);
}
}
@@ -8297,10 +8532,11 @@
ArrayList<String> pkgList = new ArrayList<String>();
Set<SdInstallArgs> keys = processCids.keySet();
for (SdInstallArgs args : keys) {
- String cid = args.cid;
String codePath = processCids.get(args);
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to install pkg : "
+ + args.cid + " from " + args.cachePath);
if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) {
- Log.i(TAG, "Failed to install package: " + codePath + " from sdcard");
+ Log.e(TAG, "Failed to install package: " + codePath + " from sdcard");
continue;
}
// Parse package
@@ -8311,7 +8547,8 @@
final PackageParser.Package pkg = pp.parsePackage(new File(codePath),
codePath, mMetrics, parseFlags);
if (pkg == null) {
- Log.w(TAG, "Failed to install package : " + cid + " from sd card");
+ Log.e(TAG, "Trying to install pkg : "
+ + args.cid + " from " + args.cachePath);
continue;
}
setApplicationInfoPaths(pkg, codePath, codePath);
@@ -8332,22 +8569,24 @@
}
}
args.doPostInstall(retCode);
- pkgList.add(pkg.packageName);
}
// Send broadcasts first
- sendResourcesChangedBroadcast(true, pkgList, uidArr);
- Runtime.getRuntime().gc();
- // If something failed do we clean up here or next install?
+ if (pkgList.size() > 0) {
+ sendResourcesChangedBroadcast(true, pkgList, uidArr);
+ Runtime.getRuntime().gc();
+ // If something failed do we clean up here or next install?
+ }
}
void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) {
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages");
ArrayList<String> pkgList = new ArrayList<String>();
+ ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>();
Set<SdInstallArgs> keys = processCids.keySet();
for (SdInstallArgs args : keys) {
String cid = args.cid;
String pkgName = args.getPackageName();
- // STOPSHIP Send broadcast to apps to remove references
- // STOPSHIP Unmount package
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to unload pkg : " + pkgName);
// Delete package internally
PackageRemovedInfo outInfo = new PackageRemovedInfo();
synchronized (mInstallLock) {
@@ -8357,14 +8596,17 @@
pkgList.add(pkgName);
} else {
Log.e(TAG, "Failed to delete pkg from sdcard : " + pkgName);
+ failedList.add(args);
}
}
}
// Send broadcasts
- sendResourcesChangedBroadcast(false, pkgList, uidArr);
- Runtime.getRuntime().gc();
+ if (pkgList.size() > 0) {
+ sendResourcesChangedBroadcast(false, pkgList, uidArr);
+ Runtime.getRuntime().gc();
+ }
// Do clean up. Just unmount
- for (SdInstallArgs args : keys) {
+ for (SdInstallArgs args : failedList) {
synchronized (mInstallLock) {
args.doPostDeleteLI(false);
}
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index e14a973..1ccae86 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -41,7 +41,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.IMountService;
+import android.os.storage.IMountService;
import android.os.IPowerManager;
import android.os.LocalPowerManager;
import android.os.Power;
@@ -212,11 +212,13 @@
private Sensor mLightSensor;
private boolean mLightSensorEnabled;
private float mLightSensorValue = -1;
+ private int mHighestLightSensorValue = -1;
private float mLightSensorPendingValue = -1;
private int mLightSensorScreenBrightness = -1;
private int mLightSensorButtonBrightness = -1;
private int mLightSensorKeyboardBrightness = -1;
private boolean mDimScreen = true;
+ private boolean mIsDocked = false;
private long mNextTimeout;
private volatile int mPokey = 0;
private volatile boolean mPokeAwakeOnSet = false;
@@ -363,6 +365,15 @@
}
}
+ private final class DockReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ dockStateChanged(state);
+ }
+ }
+
/**
* Set the setting that determines whether the device stays on when plugged in.
* The argument is a bit string, with each bit specifying a power source that,
@@ -527,6 +538,9 @@
filter = new IntentFilter();
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
mContext.registerReceiver(new BootCompletedReceiver(), filter);
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DOCK_EVENT);
+ mContext.registerReceiver(new DockReceiver(), filter);
// Listen for secure settings changes
mContext.getContentResolver().registerContentObserver(
@@ -1389,6 +1403,8 @@
// clear current value so we will update based on the new conditions
// when the sensor is reenabled.
mLightSensorValue = -1;
+ // reset our highest light sensor value when the screen turns off
+ mHighestLightSensorValue = -1;
}
}
}
@@ -2059,15 +2075,40 @@
}
};
+ private void dockStateChanged(int state) {
+ synchronized (mLocks) {
+ mIsDocked = (state != Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ if (mIsDocked) {
+ mHighestLightSensorValue = -1;
+ }
+ if ((mPowerState & SCREEN_ON_BIT) != 0) {
+ // force lights recalculation
+ int value = (int)mLightSensorValue;
+ mLightSensorValue = -1;
+ lightSensorChangedLocked(value);
+ }
+ }
+ }
+
private void lightSensorChangedLocked(int value) {
if (mDebugLightSensor) {
Log.d(TAG, "lightSensorChangedLocked " + value);
}
+ // do not allow light sensor value to decrease
+ if (mHighestLightSensorValue < value) {
+ mHighestLightSensorValue = value;
+ }
+
if (mLightSensorValue != value) {
mLightSensorValue = value;
if ((mPowerState & BATTERY_LOW_BIT) == 0) {
- int lcdValue = getAutoBrightnessValue(value, mLcdBacklightValues);
+ // use maximum light sensor value seen since screen went on for LCD to avoid flicker
+ // we only do this if we are undocked, since lighting should be stable when
+ // stationary in a dock.
+ int lcdValue = getAutoBrightnessValue(
+ (mIsDocked ? value : mHighestLightSensorValue),
+ mLcdBacklightValues);
int buttonValue = getAutoBrightnessValue(value, mButtonBacklightValues);
int keyboardValue;
if (mKeyboardVisible) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 22447ed..6e9c21b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -273,6 +273,17 @@
}
try {
+ /*
+ * NotificationManagerService is dependant on MountService,
+ * (for media / usb notifications) so we must start MountService first.
+ */
+ Log.i(TAG, "Mount Service");
+ ServiceManager.addService("mount", new MountService(context));
+ } catch (Throwable e) {
+ Log.e(TAG, "Failure starting Mount Service", e);
+ }
+
+ try {
Log.i(TAG, "Notification Manager");
notification = new NotificationManagerService(context, statusBar, lights);
ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
@@ -281,14 +292,6 @@
}
try {
- // MountService must start after NotificationManagerService
- Log.i(TAG, "Mount Service");
- ServiceManager.addService("mount", new MountService(context));
- } catch (Throwable e) {
- Log.e(TAG, "Failure starting Mount Service", e);
- }
-
- try {
Log.i(TAG, "Device Storage Monitor");
ServiceManager.addService(DeviceStorageMonitorService.SERVICE,
new DeviceStorageMonitorService(context));
@@ -529,7 +532,8 @@
// The system server has to run all of the time, so it needs to be
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
-
+ VMRuntime.getRuntime().startJitCompilation();
+
System.loadLibrary("android_servers");
init1(args);
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5e6881c..98ded37 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -11941,7 +11941,7 @@
intent.getAction());
if (intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
|| intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
- || Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(intent.getAction())
+ || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())
|| uidRemoved) {
if (checkComponentPermission(
android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
@@ -11960,7 +11960,7 @@
} else {
// If resources are unvailble just force stop all
// those packages and flush the attribute cache as well.
- if (Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(intent.getAction())) {
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())) {
String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
if (list != null && (list.length > 0)) {
for (String pkg : list) {
@@ -12146,7 +12146,7 @@
skipPackages = new String[] { pkgName };
}
}
- } else if (intent.ACTION_MEDIA_RESOURCES_AVAILABLE.equals(intent.getAction())) {
+ } else if (intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
}
if (skipPackages != null && (skipPackages.length > 0)) {
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
new file mode 100644
index 0000000..f685383
--- /dev/null
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.ArrayList;
+/**
+ * @hide
+ */
+public class Tethering extends INetworkManagementEventObserver.Stub {
+
+ private Notification mTetheringNotification;
+ private Context mContext;
+ private final String TAG = "Tethering";
+
+ private boolean mPlaySounds = false;
+
+ private ArrayList<String> mAvailableIfaces;
+ private ArrayList<String> mActiveIfaces;
+
+ private ArrayList<String> mActiveTtys;
+
+ private BroadcastReceiver mStateReceiver;
+
+ public Tethering(Context context) {
+ Log.d(TAG, "Tethering starting");
+ mContext = context;
+
+ // register for notifications from NetworkManagement Service
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+ try {
+ service.registerObserver(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error registering observer :" + e);
+ }
+
+ mAvailableIfaces = new ArrayList<String>();
+ mActiveIfaces = new ArrayList<String>();
+ mActiveTtys = new ArrayList<String>();
+
+ // TODO - remove this hack after real USB connections are detected.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_UMS_DISCONNECTED);
+ filter.addAction(Intent.ACTION_UMS_CONNECTED);
+ mStateReceiver = new UMSStateReceiver();
+ mContext.registerReceiver(mStateReceiver, filter);
+ }
+
+ public synchronized void interfaceLinkStatusChanged(String iface, boolean link) {
+ Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
+ }
+
+ public synchronized void interfaceAdded(String iface) {
+ if (mActiveIfaces.contains(iface)) {
+ Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring");
+ return;
+ }
+ if (mAvailableIfaces.contains(iface)) {
+ Log.e(TAG, "available iface (" + iface + ") readded, ignoring");
+ return;
+ }
+ mAvailableIfaces.add(iface);
+ Log.d(TAG, "interfaceAdded :" + iface);
+ sendTetherStateChangedBroadcast();
+ }
+
+ public synchronized void interfaceRemoved(String iface) {
+ if (mActiveIfaces.contains(iface)) {
+ Log.d(TAG, "removed an active iface (" + iface + ")");
+ untether(iface);
+ }
+ if (mAvailableIfaces.contains(iface)) {
+ mAvailableIfaces.remove(iface);
+ Log.d(TAG, "interfaceRemoved " + iface);
+ sendTetherStateChangedBroadcast();
+ }
+ }
+
+ public synchronized boolean tether(String iface) {
+ Log.d(TAG, "Tethering " + iface);
+
+ if (!mAvailableIfaces.contains(iface)) {
+ Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
+ return false;
+ }
+ if (mActiveIfaces.contains(iface)) {
+ Log.e(TAG, "Tried to Tether an already Tethered iface :" + iface + ", ignoring");
+ return false;
+ }
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+
+ if (mActiveIfaces.size() == 0) {
+ try {
+ service.setIpForwardingEnabled(true);
+ } catch (Exception e) {
+ Log.e(TAG, "Error in setIpForwardingEnabled(true) :" + e);
+ return false;
+ }
+
+ try {
+ // TODO - don't hardcode this - though with non-conf values (un-routable)
+ // maybe it's not a big deal
+ service.startTethering("169.254.2.1", "169.254.2.64");
+ } catch (Exception e) {
+ Log.e(TAG, "Error in startTethering :" + e);
+ try {
+ service.setIpForwardingEnabled(false);
+ } catch (Exception ee) {}
+ return false;
+ }
+
+ try {
+ // TODO - maybe use the current connection's dns servers for this
+ String[] dns = new String[2];
+ dns[0] = new String("8.8.8.8");
+ dns[1] = new String("4.2.2.2");
+ service.setDnsForwarders(dns);
+ } catch (Exception e) {
+ Log.e(TAG, "Error in setDnsForwarders :" + e);
+ try {
+ service.stopTethering();
+ } catch (Exception ee) {}
+ try {
+ service.setIpForwardingEnabled(false);
+ } catch (Exception ee) {}
+ }
+ }
+
+ try {
+ service.tetherInterface(iface);
+ } catch (Exception e) {
+ Log.e(TAG, "Error in tetherInterface :" + e);
+ if (mActiveIfaces.size() == 0) {
+ try {
+ service.stopTethering();
+ } catch (Exception ee) {}
+ try {
+ service.setIpForwardingEnabled(false);
+ } catch (Exception ee) {}
+ }
+ return false;
+ }
+
+ try {
+ // TODO - use the currently active external iface
+ service.enableNat (iface, "rmnet0");
+ } catch (Exception e) {
+ Log.e(TAG, "Error in enableNat :" + e);
+ try {
+ service.untetherInterface(iface);
+ } catch (Exception ee) {}
+ if (mActiveIfaces.size() == 0) {
+ try {
+ service.stopTethering();
+ } catch (Exception ee) {}
+ try {
+ service.setIpForwardingEnabled(false);
+ } catch (Exception ee) {}
+ }
+ return false;
+ }
+ mAvailableIfaces.remove(iface);
+ mActiveIfaces.add(iface);
+ Log.d(TAG, "Tethered " + iface);
+ sendTetherStateChangedBroadcast();
+ return true;
+ }
+
+ public synchronized boolean untether(String iface) {
+ Log.d(TAG, "Untethering " + iface);
+
+ if (mAvailableIfaces.contains(iface)) {
+ Log.e(TAG, "Tried to Untether an available iface :" + iface);
+ return false;
+ }
+ if (!mActiveIfaces.contains(iface)) {
+ Log.e(TAG, "Tried to Untether an inactive iface :" + iface);
+ return false;
+ }
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+
+ // none of these errors are recoverable - ie, multiple calls won't help
+ // and the user can't do anything. Basically a reboot is required and probably
+ // the device is misconfigured or something bad has happend.
+ // Because of this, we should try to unroll as much state as we can.
+ try {
+ service.disableNat(iface, "rmnet0");
+ } catch (Exception e) {
+ Log.e(TAG, "Error in disableNat :" + e);
+ }
+ try {
+ service.untetherInterface(iface);
+ } catch (Exception e) {
+ Log.e(TAG, "Error untethering " + iface + ", :" + e);
+ }
+ mActiveIfaces.remove(iface);
+ mAvailableIfaces.add(iface);
+
+ if (mActiveIfaces.size() == 0) {
+ Log.d(TAG, "no active tethers - turning down dhcp/ipforward");
+ try {
+ service.stopTethering();
+ } catch (Exception e) {
+ Log.e(TAG, "Error in stopTethering :" + e);
+ }
+ try {
+ service.setIpForwardingEnabled(false);
+ } catch (Exception e) {
+ Log.e(TAG, "Error in setIpForwardingEnabled(false) :" + e);
+ }
+ }
+ sendTetherStateChangedBroadcast();
+ Log.d(TAG, "Untethered " + iface);
+ return true;
+ }
+
+ private void sendTetherStateChangedBroadcast() {
+ Intent broadcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+ broadcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ broadcast.putExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER_COUNT,
+ mAvailableIfaces.size());
+ broadcast.putExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER_COUNT, mActiveIfaces.size());
+ mContext.sendBroadcast(broadcast);
+
+ // for USB we only have the one, so don't have to deal with additional
+ if (mAvailableIfaces.size() > 0) {
+ // Check if the user wants to be bothered
+ boolean tellUser = (Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.TETHER_NOTIFY, 0) == 1);
+
+ if (tellUser) {
+ showTetherAvailableNotification();
+ }
+ } else if (mActiveIfaces.size() > 0) {
+ showTetheredNotification();
+ } else {
+ clearNotification();
+ }
+ }
+
+ private void showTetherAvailableNotification() {
+ NotificationManager notificationManager = (NotificationManager)mContext.
+ getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager == null) {
+ return;
+ }
+
+ Intent intent = new Intent();
+ intent.setClass(mContext, com.android.internal.app.TetherActivity.class);
+
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+ Resources r = Resources.getSystem();
+ CharSequence title = r.getText(com.android.internal.R.string.
+ tether_available_notification_title);
+ CharSequence message = r.getText(com.android.internal.R.string.
+ tether_available_notification_message);
+
+ if(mTetheringNotification == null) {
+ mTetheringNotification = new Notification();
+ mTetheringNotification.when = 0;
+ }
+ mTetheringNotification.icon = com.android.internal.R.drawable.stat_sys_tether_usb;
+
+ boolean playSounds = false;
+ //playSounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");
+ if (playSounds) {
+ mTetheringNotification.defaults |= Notification.DEFAULT_SOUND;
+ } else {
+ mTetheringNotification.defaults &= ~Notification.DEFAULT_SOUND;
+ }
+
+ mTetheringNotification.flags = Notification.FLAG_ONGOING_EVENT;
+ mTetheringNotification.tickerText = title;
+ mTetheringNotification.setLatestEventInfo(mContext, title, message, pi);
+
+ notificationManager.notify(mTetheringNotification.icon, mTetheringNotification);
+
+ }
+
+ private void showTetheredNotification() {
+ NotificationManager notificationManager = (NotificationManager)mContext.
+ getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager == null) {
+ return;
+ }
+
+ Intent intent = new Intent();
+ intent.setClass(mContext, com.android.internal.app.TetherActivity.class);
+
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+ Resources r = Resources.getSystem();
+ CharSequence title = r.getText(com.android.internal.R.string.
+ tether_stop_notification_title);
+ CharSequence message = r.getText(com.android.internal.R.string.
+ tether_stop_notification_message);
+
+ if(mTetheringNotification == null) {
+ mTetheringNotification = new Notification();
+ mTetheringNotification.when = 0;
+ }
+ mTetheringNotification.icon = com.android.internal.R.drawable.stat_sys_tether_usb;
+
+ boolean playSounds = false;
+ //playSounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");
+ if (playSounds) {
+ mTetheringNotification.defaults |= Notification.DEFAULT_SOUND;
+ } else {
+ mTetheringNotification.defaults &= ~Notification.DEFAULT_SOUND;
+ }
+
+ mTetheringNotification.flags = Notification.FLAG_ONGOING_EVENT;
+ mTetheringNotification.tickerText = title;
+ mTetheringNotification.setLatestEventInfo(mContext, title, message, pi);
+
+ notificationManager.notify(mTetheringNotification.icon, mTetheringNotification);
+ }
+
+ private void clearNotification() {
+ NotificationManager notificationManager = (NotificationManager)mContext.
+ getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager != null && mTetheringNotification != null) {
+ notificationManager.cancel(mTetheringNotification.icon);
+ mTetheringNotification = null;
+ }
+ }
+
+
+
+
+// TODO - remove this hack after we get proper USB detection
+ private class UMSStateReceiver extends BroadcastReceiver {
+ public void onReceive(Context content, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_UMS_CONNECTED)) {
+ Tethering.this.handleTtyConnect();
+ } else if (intent.getAction().equals(Intent.ACTION_UMS_DISCONNECTED)) {
+ Tethering.this.handleTtyDisconnect();
+ }
+ }
+ }
+
+ private synchronized void handleTtyConnect() {
+ Log.d(TAG, "handleTtyConnect");
+ // for each of the available Tty not already supported by a ppp session,
+ // create a ppp session
+ // TODO - this should be data-driven rather than hard coded.
+ String[] allowedTtys = new String[1];
+ allowedTtys[0] = new String("ttyGS0");
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+
+ String[] availableTtys;
+ try {
+ availableTtys = service.listTtys();
+ } catch (RemoteException e) {
+ Log.e(TAG, "error listing Ttys :" + e);
+ return;
+ }
+
+ for (String tty : availableTtys) {
+ for (String pattern : allowedTtys) {
+ if (tty.matches(pattern)) {
+ synchronized (this) {
+ if (!mActiveTtys.contains(tty)) {
+ // TODO - don't hardcode this
+ try {
+ // local, remote, dns
+ service.attachPppd(tty, "169.254.1.128", "169.254.1.1",
+ "169.254.1.128", "0.0.0.0");
+ } catch (Exception e) {
+ Log.e(TAG, "error calling attachPppd: " + e);
+ return;
+ }
+ Log.d(TAG, "started Pppd on tty " + tty);
+ mActiveTtys.add(tty);
+ // TODO - remove this after we detect the new iface
+ interfaceAdded("ppp0");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private synchronized void handleTtyDisconnect() {
+ Log.d(TAG, "handleTtyDisconnect");
+
+ // TODO - this should be data-driven rather than hard coded.
+ String[] allowedTtys = new String[1];
+ allowedTtys[0] = new String("ttyGS0");
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+
+ String[] availableTtys;
+ try {
+ availableTtys = service.listTtys();
+ } catch (RemoteException e) {
+ Log.e(TAG, "error listing Ttys :" + e);
+ return;
+ }
+
+ for (String tty : availableTtys) {
+ for (String pattern : allowedTtys) {
+ if (tty.matches(pattern)) {
+ synchronized (this) {
+ if (mActiveTtys.contains(tty)) {
+ try {
+ service.detachPppd(tty);
+ } catch (Exception e) {
+ Log.e(TAG, "error calling detachPppd on " + tty + " :" + e);
+ }
+ mActiveTtys.remove(tty);
+ // TODO - remove this after we detect the new iface
+ interfaceRemoved("ppp0");
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public synchronized String[] getTetheredIfaces() {
+ int size = mActiveIfaces.size();
+ String[] result = new String[size];
+ size -= 1;
+ for (int i=0; i< size; i++) {
+ result[i] = mActiveIfaces.get(i);
+ }
+ return result;
+ }
+
+ public synchronized String[] getTetherableIfaces() {
+ int size = mAvailableIfaces.size();
+ String[] result = new String[size];
+ size -= 1;
+ for (int i=0; i< size; i++) {
+ result[i] = mActiveIfaces.get(i);
+ }
+ return result;
+ }
+}
diff --git a/services/java/com/android/server/status/NotificationData.java b/services/java/com/android/server/status/NotificationData.java
index 0a3411a1..784b781 100644
--- a/services/java/com/android/server/status/NotificationData.java
+++ b/services/java/com/android/server/status/NotificationData.java
@@ -19,7 +19,7 @@
public PendingIntent deleteIntent;
public String toString() {
- return "NotificationData(package=" + pkg + " tickerText=" + tickerText
+ return "NotificationData(package=" + pkg + " id=" + id + " tickerText=" + tickerText
+ " ongoingEvent=" + ongoingEvent + " contentIntent=" + contentIntent
+ " deleteIntent=" + deleteIntent
+ " clearable=" + clearable
diff --git a/services/java/com/android/server/status/NotificationViewList.java b/services/java/com/android/server/status/NotificationViewList.java
index 6229292..8f1633f 100644
--- a/services/java/com/android/server/status/NotificationViewList.java
+++ b/services/java/com/android/server/status/NotificationViewList.java
@@ -104,10 +104,25 @@
return null;
}
- // gets the index of the notification in its expanded parent view
+ // gets the index of the notification's view in its expanded parent view
int getExpandedIndex(StatusBarNotification notification) {
ArrayList<StatusBarNotification> list = notification.data.ongoingEvent ? mOngoing : mLatest;
- return list.size() - indexForKey(list, notification.key) - 1;
+ final IBinder key = notification.key;
+ int index = 0;
+ // (the view order is backwards from this list order)
+ for (int i=list.size()-1; i>=0; i--) {
+ StatusBarNotification item = list.get(i);
+ if (item.key == key) {
+ return index;
+ }
+ if (item.view != null) {
+ index++;
+ }
+ }
+ Log.e(StatusBarService.TAG, "Couldn't find notification in NotificationViewList.");
+ Log.e(StatusBarService.TAG, "notification=" + notification);
+ dump(notification);
+ return 0;
}
void clearViews() {
@@ -156,6 +171,13 @@
list.add(index, notification);
if (StatusBarService.SPEW) {
+ Log.d(StatusBarService.TAG, "NotificationViewList index=" + index);
+ dump(notification);
+ }
+ }
+
+ void dump(StatusBarNotification notification) {
+ if (StatusBarService.SPEW) {
String s = "";
for (int i=0; i<mOngoing.size(); i++) {
StatusBarNotification that = mOngoing.get(i);
@@ -168,7 +190,7 @@
}
s += " ";
}
- Log.d(StatusBarService.TAG, "NotificationViewList ongoing index=" + index + ": " + s);
+ Log.d(StatusBarService.TAG, "NotificationViewList ongoing: " + s);
s = "";
for (int i=0; i<mLatest.size(); i++) {
@@ -182,7 +204,7 @@
}
s += " ";
}
- Log.d(StatusBarService.TAG, "NotificationViewList latest index=" + index + ": " + s);
+ Log.d(StatusBarService.TAG, "NotificationViewList latest: " + s);
}
}
diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java
index 7b722a5..c7c3335 100644
--- a/services/java/com/android/server/status/StatusBarService.java
+++ b/services/java/com/android/server/status/StatusBarService.java
@@ -687,7 +687,8 @@
&& (oldData == null
|| oldData.tickerText == null
|| !CharSequences.equals(oldData.tickerText, n.tickerText))) {
- if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) {
+ if (0 == (mDisabled &
+ (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
mTicker.addEntry(n, StatusBarIcon.getIcon(mContext, data), n.tickerText);
}
}
@@ -886,7 +887,8 @@
&& n.contentView.getPackage() != null
&& oldData.contentView.getPackage() != null
&& oldData.contentView.getPackage().equals(n.contentView.getPackage())
- && oldData.contentView.getLayoutId() == n.contentView.getLayoutId()) {
+ && oldData.contentView.getLayoutId() == n.contentView.getLayoutId()
+ && notification.view != null) {
mNotificationData.update(notification);
try {
n.contentView.reapply(mContext, notification.contentView);
@@ -1697,6 +1699,10 @@
setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
}
}
+ } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+ if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+ mTicker.halt();
+ }
}
}
@@ -1807,14 +1813,14 @@
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
filter.addDataScheme("package");
mContext.registerReceiver(this, filter);
- IntentFilter sdFilter = new IntentFilter(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(this, sdFilter);
}
@Override
public void onReceive(Context context, Intent intent) {
String pkgList[] = null;
- if (Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(intent.getAction())) {
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else {
Uri data = intent.getData();
diff --git a/test-runner/android/test/SyncBaseInstrumentation.java b/test-runner/android/test/SyncBaseInstrumentation.java
index e8d72d9..7d418f0 100644
--- a/test-runner/android/test/SyncBaseInstrumentation.java
+++ b/test-runner/android/test/SyncBaseInstrumentation.java
@@ -46,7 +46,7 @@
*/
protected void syncProvider(Uri uri, String accountName, String authority) throws Exception {
Bundle extras = new Bundle();
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
Account account = new Account(accountName, "com.google");
ContentResolver.requestSync(account, authority, extras);
diff --git a/test-runner/android/test/TouchUtils.java b/test-runner/android/test/TouchUtils.java
index 962b2f9..69c6d2d 100644
--- a/test-runner/android/test/TouchUtils.java
+++ b/test-runner/android/test/TouchUtils.java
@@ -773,7 +773,7 @@
float xStep = (toX - fromX) / stepCount;
MotionEvent event = MotionEvent.obtain(downTime, eventTime,
- MotionEvent.ACTION_DOWN, fromX, y, 0);
+ MotionEvent.ACTION_DOWN, x, y, 0);
inst.sendPointerSync(event);
inst.waitForIdleSync();
@@ -787,7 +787,7 @@
}
eventTime = SystemClock.uptimeMillis();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, fromX, y, 0);
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
inst.sendPointerSync(event);
inst.waitForIdleSync();
}
diff --git a/tests/AndroidTests/Android.mk b/tests/AndroidTests/Android.mk
index bff8fba..0d29c35 100644
--- a/tests/AndroidTests/Android.mk
+++ b/tests/AndroidTests/Android.mk
@@ -5,8 +5,6 @@
LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner services
-LOCAL_STATIC_JAVA_LIBRARIES := gsf-client
-
# Resource unit tests use a private locale
LOCAL_AAPT_FLAGS = -c xx_YY -c cs -c 160dpi -c 32dpi -c 240dpi
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index c2548b9..2b6b81e 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -26,6 +26,9 @@
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.DELETE_PACKAGES" />
+ <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
<uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
diff --git a/tests/AndroidTests/res/raw/install b/tests/AndroidTests/res/raw/install
new file mode 100644
index 0000000..2ee1f3c
--- /dev/null
+++ b/tests/AndroidTests/res/raw/install
Binary files differ
diff --git a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
new file mode 100755
index 0000000..3a4d38c
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
@@ -0,0 +1,771 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.unit_tests;
+
+import android.os.storage.IMountService.Stub;
+
+import android.net.Uri;
+import android.os.FileUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageStats;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.storage.IMountService;
+import android.os.storage.StorageResultCode;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+
+public class PackageManagerTests extends AndroidTestCase {
+ private static final boolean localLOGV = true;
+ public static final String TAG="PackageManagerTests";
+ public final long MAX_WAIT_TIME=120*1000;
+ public final long WAIT_TIME_INCR=20*1000;
+
+ void failStr(String errMsg) {
+ Log.w(TAG, "errMsg="+errMsg);
+ fail(errMsg);
+ }
+ void failStr(Exception e) {
+ Log.w(TAG, "e.getMessage="+e.getMessage());
+ Log.w(TAG, "e="+e);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ private class PackageInstallObserver extends IPackageInstallObserver.Stub {
+ public int returnCode;
+ private boolean doneFlag = false;
+
+ public void packageInstalled(String packageName, int returnCode) {
+ synchronized(this) {
+ this.returnCode = returnCode;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+ }
+
+ abstract class GenericReceiver extends BroadcastReceiver {
+ private boolean doneFlag = false;
+ boolean received = false;
+ Intent intent;
+ IntentFilter filter;
+ abstract boolean notifyNow(Intent intent);
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (notifyNow(intent)) {
+ synchronized (this) {
+ received = true;
+ doneFlag = true;
+ this.intent = intent;
+ notifyAll();
+ }
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+
+ public void setFilter(IntentFilter filter) {
+ this.filter = filter;
+ }
+ }
+
+ class InstallReceiver extends GenericReceiver {
+ String pkgName;
+
+ InstallReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addDataScheme("package");
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ return false;
+ }
+ Uri data = intent.getData();
+ String installedPkg = data.getEncodedSchemeSpecificPart();
+ if (pkgName.equals(installedPkg)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ PackageManager getPm() {
+ return mContext.getPackageManager();
+ }
+
+ public boolean invokeInstallPackage(Uri packageURI, int flags,
+ final String pkgName, GenericReceiver receiver) throws Exception {
+ PackageInstallObserver observer = new PackageInstallObserver();
+ final boolean received = false;
+ mContext.registerReceiver(receiver, receiver.filter);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ synchronized (receiver) {
+ getPm().installPackage(packageURI, observer, flags, null);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for packageInstalled callback");
+ }
+ if (observer.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ return false;
+ }
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ throw new Exception("Timed out waiting for PACKAGE_ADDED notification");
+ }
+ return receiver.received;
+ }
+ }
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ Uri getInstallablePackage(int fileResId, File outFile) {
+ Resources res = mContext.getResources();
+ InputStream is = null;
+ try {
+ is = res.openRawResource(fileResId);
+ } catch (NotFoundException e) {
+ failStr("Failed to load resource with id: " + fileResId);
+ }
+ FileUtils.setPermissions(outFile.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
+ -1, -1);
+ assertTrue(FileUtils.copyToFile(is, outFile));
+ FileUtils.setPermissions(outFile.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
+ -1, -1);
+ return Uri.fromFile(outFile);
+ }
+
+ private PackageParser.Package parsePackage(Uri packageURI) {
+ final String archiveFilePath = packageURI.getPath();
+ PackageParser packageParser = new PackageParser(archiveFilePath);
+ File sourceFile = new File(archiveFilePath);
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
+ }
+
+ private void assertInstall(String pkgName, int flags) {
+ try {
+ ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
+ assertNotNull(info);
+ assertEquals(pkgName, info.packageName);
+ File dataDir = Environment.getDataDirectory();
+ String appInstallPath = new File(dataDir, "app").getPath();
+ String drmInstallPath = new File(dataDir, "app-private").getPath();
+ File srcDir = new File(info.sourceDir);
+ String srcPath = srcDir.getParent();
+ File publicSrcDir = new File(info.publicSourceDir);
+ String publicSrcPath = publicSrcDir.getParent();
+
+ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
+ assertTrue((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
+ assertEquals(srcPath, drmInstallPath);
+ assertEquals(publicSrcPath, appInstallPath);
+ } else {
+ assertFalse((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
+ if ((flags & PackageManager.INSTALL_ON_SDCARD) != 0) {
+ assertTrue((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0);
+ // Hardcoded for now
+ assertTrue(srcPath.startsWith("/asec"));
+ assertTrue(publicSrcPath.startsWith("/asec"));
+ } else {
+ assertEquals(srcPath, appInstallPath);
+ assertEquals(publicSrcPath, appInstallPath);
+ assertFalse((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0);
+ }
+ }
+ } catch (NameNotFoundException e) {
+ failStr("failed with exception : " + e);
+ }
+ }
+
+ class InstallParams {
+ String outFileName;
+ Uri packageURI;
+ PackageParser.Package pkg;
+ InstallParams(PackageParser.Package pkg, String outFileName, Uri packageURI) {
+ this.outFileName = outFileName;
+ this.packageURI = packageURI;
+ this.pkg = pkg;
+ }
+ }
+
+ /*
+ * Utility function that reads a apk bundled as a raw resource
+ * copies it into own data directory and invokes
+ * PackageManager api to install it.
+ */
+ public InstallParams installFromRawResource(int flags, boolean cleanUp) {
+ String outFileName = "install.apk";
+ File filesDir = mContext.getFilesDir();
+ File outFile = new File(filesDir, outFileName);
+ Uri packageURI = getInstallablePackage(R.raw.install, outFile);
+ PackageParser.Package pkg = parsePackage(packageURI);
+ assertNotNull(pkg);
+ InstallReceiver receiver = new InstallReceiver(pkg.packageName);
+ InstallParams ip = null;
+ try {
+ try {
+ assertTrue(invokeInstallPackage(packageURI, flags,
+ pkg.packageName, receiver));
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ }
+ // Verify installed information
+ assertInstall(pkg.packageName, flags);
+ ip = new InstallParams(pkg, outFileName, packageURI);
+ return ip;
+ } finally {
+ if (cleanUp) {
+ cleanUpInstall(ip);
+ }
+ }
+ }
+
+ @MediumTest
+ public void testInstallNormalInternal() {
+ installFromRawResource(0, true);
+ }
+
+ @MediumTest
+ public void testInstallFwdLockedInternal() {
+ installFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, true);
+ }
+
+ @MediumTest
+ public void testInstallSdcard() {
+ installFromRawResource(PackageManager.INSTALL_ON_SDCARD, true);
+ }
+
+ /* ------------------------- Test replacing packages --------------*/
+ class ReplaceReceiver extends GenericReceiver {
+ String pkgName;
+ final static int INVALID = -1;
+ final static int REMOVED = 1;
+ final static int ADDED = 2;
+ final static int REPLACED = 3;
+ int removed = INVALID;
+ // for updated system apps only
+ boolean update = false;
+
+ ReplaceReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ if (update) {
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ }
+ filter.addDataScheme("package");
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ Uri data = intent.getData();
+ String installedPkg = data.getEncodedSchemeSpecificPart();
+ if (pkgName == null || !pkgName.equals(installedPkg)) {
+ return false;
+ }
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ removed = REMOVED;
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ if (removed != REMOVED) {
+ return false;
+ }
+ boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ if (!replacing) {
+ return false;
+ }
+ removed = ADDED;
+ if (!update) {
+ return true;
+ }
+ } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+ if (removed != ADDED) {
+ return false;
+ }
+ removed = REPLACED;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /*
+ * Utility function that reads a apk bundled as a raw resource
+ * copies it into own data directory and invokes
+ * PackageManager api to install first and then replace it
+ * again.
+ */
+ public void replaceFromRawResource(int flags) {
+ InstallParams ip = installFromRawResource(flags, false);
+ boolean replace = ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0);
+ GenericReceiver receiver;
+ if (replace) {
+ receiver = new ReplaceReceiver(ip.pkg.packageName);
+ Log.i(TAG, "Creating replaceReceiver");
+ } else {
+ receiver = new InstallReceiver(ip.pkg.packageName);
+ }
+ try {
+ try {
+ assertEquals(invokeInstallPackage(ip.packageURI, flags,
+ ip.pkg.packageName, receiver), replace);
+ if (replace) {
+ assertInstall(ip.pkg.packageName, flags);
+ }
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ }
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ @MediumTest
+ public void testReplaceFailNormalInternal() {
+ replaceFromRawResource(0);
+ }
+
+ @MediumTest
+ public void testReplaceFailFwdLockedInternal() {
+ replaceFromRawResource(PackageManager.INSTALL_FORWARD_LOCK);
+ }
+
+ @MediumTest
+ public void testReplaceFailSdcard() {
+ replaceFromRawResource(PackageManager.INSTALL_ON_SDCARD);
+ }
+
+ @MediumTest
+ public void testReplaceNormalInternal() {
+ replaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING);
+ }
+
+ @MediumTest
+ public void testReplaceFwdLockedInternal() {
+ replaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING |
+ PackageManager.INSTALL_FORWARD_LOCK);
+ }
+
+ @MediumTest
+ public void testReplaceSdcard() {
+ replaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING |
+ PackageManager.INSTALL_ON_SDCARD);
+ }
+
+ /* -------------- Delete tests ---*/
+ class DeleteObserver extends IPackageDeleteObserver.Stub {
+
+ public boolean succeeded;
+ private boolean doneFlag = false;
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+
+ public void packageDeleted(boolean succeeded) throws RemoteException {
+ synchronized(this) {
+ this.succeeded = succeeded;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+ }
+
+ class DeleteReceiver extends GenericReceiver {
+ String pkgName;
+
+ DeleteReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ if (!Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ return false;
+ }
+ Uri data = intent.getData();
+ String installedPkg = data.getEncodedSchemeSpecificPart();
+ if (pkgName.equals(installedPkg)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public boolean invokeDeletePackage(Uri packageURI, int flags,
+ final String pkgName, GenericReceiver receiver) throws Exception {
+ DeleteObserver observer = new DeleteObserver();
+ final boolean received = false;
+ mContext.registerReceiver(receiver, receiver.filter);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ synchronized (receiver) {
+ getPm().deletePackage(pkgName, observer, flags);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for packageInstalled callback");
+ }
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ throw new Exception("Timed out waiting for PACKAGE_ADDED notification");
+ }
+ return receiver.received;
+ }
+ }
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ public void deleteFromRawResource(int iFlags, int dFlags) {
+ InstallParams ip = installFromRawResource(iFlags, false);
+ boolean retainData = ((dFlags & PackageManager.DONT_DELETE_DATA) != 0);
+ GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName);
+ DeleteObserver observer = new DeleteObserver();
+ try {
+ assertTrue(invokeDeletePackage(ip.packageURI, dFlags,
+ ip.pkg.packageName, receiver));
+ ApplicationInfo info = null;
+ Log.i(TAG, "okay4");
+ try {
+ info = getPm().getApplicationInfo(ip.pkg.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (NameNotFoundException e) {
+ info = null;
+ }
+ if (retainData) {
+ assertNotNull(info);
+ assertEquals(info.packageName, ip.pkg.packageName);
+ File file = new File(info.dataDir);
+ assertTrue(file.exists());
+ } else {
+ assertNull(info);
+ }
+ } catch (Exception e) {
+ failStr(e);
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ @MediumTest
+ public void testDeleteNormalInternal() {
+ deleteFromRawResource(0, 0);
+ }
+
+ @MediumTest
+ public void testDeleteFwdLockedInternal() {
+ deleteFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, 0);
+ }
+
+ @MediumTest
+ public void testDeleteSdcard() {
+ deleteFromRawResource(PackageManager.INSTALL_ON_SDCARD, 0);
+ }
+
+ @MediumTest
+ public void testDeleteNormalInternalRetainData() {
+ deleteFromRawResource(0, PackageManager.DONT_DELETE_DATA);
+ }
+
+ @MediumTest
+ public void testDeleteFwdLockedInternalRetainData() {
+ deleteFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, PackageManager.DONT_DELETE_DATA);
+ }
+
+ @MediumTest
+ public void testDeleteSdcardRetainData() {
+ deleteFromRawResource(PackageManager.INSTALL_ON_SDCARD, PackageManager.DONT_DELETE_DATA);
+ }
+
+ /* sdcard mount/unmount tests ******/
+
+ class SdMountReceiver extends GenericReceiver {
+ String pkgNames[];
+ boolean status = true;
+
+ SdMountReceiver(String[] pkgNames) {
+ this.pkgNames = pkgNames;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ Log.i(TAG, "okay 1");
+ String action = intent.getAction();
+ if (!Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ return false;
+ }
+ String rpkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (String pkg : pkgNames) {
+ boolean found = false;
+ for (String rpkg : rpkgList) {
+ if (rpkg.equals(pkg)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ status = false;
+ return true;
+ }
+ }
+ return true;
+ }
+ }
+
+ class SdUnMountReceiver extends GenericReceiver {
+ String pkgNames[];
+ boolean status = true;
+
+ SdUnMountReceiver(String[] pkgNames) {
+ this.pkgNames = pkgNames;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ if (!Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ return false;
+ }
+ String rpkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (String pkg : pkgNames) {
+ boolean found = false;
+ for (String rpkg : rpkgList) {
+ if (rpkg.equals(pkg)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ status = false;
+ return true;
+ }
+ }
+ return true;
+ }
+ }
+
+ IMountService getMs() {
+ IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IMountService.Stub.asInterface(service);
+ } else {
+ Log.e(TAG, "Can't get mount service");
+ }
+ return null;
+ }
+
+ boolean getMediaState() {
+ try {
+ String mPath = Environment.getExternalStorageDirectory().toString();
+ String state = getMs().getVolumeState(mPath);
+ return Environment.MEDIA_MOUNTED.equals(state);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ boolean mountMedia() {
+ if (getMediaState()) {
+ return true;
+ }
+ try {
+ String mPath = Environment.getExternalStorageDirectory().toString();
+ int ret = getMs().mountVolume(mPath);
+ return ret == StorageResultCode.OperationSucceeded;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ private boolean unmountMedia() {
+ if (!getMediaState()) {
+ return true;
+ }
+ try {
+ String mPath = Environment.getExternalStorageDirectory().toString();
+ int ret = getMs().unmountVolume(mPath);
+ return ret == StorageResultCode.OperationSucceeded;
+ } catch (RemoteException e) {
+ return true;
+ }
+ }
+
+ /*
+ * Install package on sdcard. Unmount and then mount the media.
+ * (Use PackageManagerService private api for now)
+ * Make sure the installed package is available.
+ * STOPSHIP will uncomment when MountService api's to mount/unmount
+ * are made asynchronous.
+ */
+ public void xxxtestMountSdNormalInternal() {
+ assertTrue(mountFromRawResource());
+ }
+
+ private boolean mountFromRawResource() {
+ // Install pkg on sdcard
+ InstallParams ip = installFromRawResource(PackageManager.INSTALL_ON_SDCARD |
+ PackageManager.INSTALL_REPLACE_EXISTING, false);
+ if (localLOGV) Log.i(TAG, "Installed pkg on sdcard");
+ boolean origState = getMediaState();
+ SdMountReceiver receiver = new SdMountReceiver(new String[]{ip.pkg.packageName});
+ try {
+ if (localLOGV) Log.i(TAG, "Unmounting media");
+ // Unmount media
+ assertTrue(unmountMedia());
+ if (localLOGV) Log.i(TAG, "Unmounted media");
+ try {
+ if (localLOGV) Log.i(TAG, "Sleeping for 10 second");
+ Thread.sleep(10*1000);
+ } catch (InterruptedException e) {
+ failStr(e);
+ }
+ // Register receiver here
+ PackageManager pm = getPm();
+ mContext.registerReceiver(receiver, receiver.filter);
+
+ // Wait on receiver
+ synchronized (receiver) {
+ if (localLOGV) Log.i(TAG, "Mounting media");
+ // Mount media again
+ assertTrue(mountMedia());
+ if (localLOGV) Log.i(TAG, "Mounted media");
+ if (localLOGV) Log.i(TAG, "Waiting for notification");
+ long waitTime = 0;
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ failStr("Timed out waiting for EXTERNAL_APPLICATIONS notification");
+ }
+ return receiver.received;
+ }
+ } catch (InterruptedException e) {
+ failStr(e);
+ return false;
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ // Restore original media state
+ if (origState) {
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ if (localLOGV) Log.i(TAG, "Cleaning up install");
+ cleanUpInstall(ip);
+ }
+ }
+
+ void cleanUpInstall(InstallParams ip) {
+ if (ip == null) {
+ return;
+ }
+ Runtime.getRuntime().gc();
+ Log.i(TAG, "Deleting package : " + ip.pkg.packageName);
+ getPm().deletePackage(ip.pkg.packageName, null, 0);
+ File outFile = new File(ip.outFileName);
+ if (outFile != null && outFile.exists()) {
+ outFile.delete();
+ }
+ }
+ /*
+ * TODO's
+ * check version numbers for upgrades
+ * check permissions of installed packages
+ * how to do tests on updated system apps?
+ * verify updates to system apps cannot be installed on the sdcard.
+ */
+}
diff --git a/tests/CoreTests/android/content/SyncQueueTest.java b/tests/CoreTests/android/content/SyncQueueTest.java
index 5f4ab78..1da59d1 100644
--- a/tests/CoreTests/android/content/SyncQueueTest.java
+++ b/tests/CoreTests/android/content/SyncQueueTest.java
@@ -66,22 +66,22 @@
long now = SystemClock.elapsedRealtime() + 200;
- assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op6);
- assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op1);
- assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op4);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op5);
- assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op2);
- assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op3);
}
@@ -109,32 +109,32 @@
long now = SystemClock.elapsedRealtime() + 200;
- assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op6);
- assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op1);
mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
- assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0);
- assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op4);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op5);
- assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op2);
- assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+ assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op3);
}
diff --git a/tests/CoreTests/android/core/StringTest.java b/tests/CoreTests/android/core/StringTest.java
deleted file mode 100644
index 957dd40..0000000
--- a/tests/CoreTests/android/core/StringTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.
- */
-
-package android.core;
-
-import com.ibm.icu4jni.text.RuleBasedNumberFormat;
-import com.ibm.icu4jni.text.RuleBasedNumberFormat.RBNFType;
-
-import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-/**
- * Some String tests.
- */
-public class StringTest extends TestCase {
- private String germanSpelloutRule = "%alt-ones:" +
- " -x: minus >>;" +
- " x.x: << komma >>;" +
- " null; eins; =%%main=;" +
- "%%main:" +
- " null; ein; zwei; drei; vier; f\u00fcnf; sechs; sieben; acht; neun;\n" +
- " zehn; elf; zwu00f6lf; >>zehn;" +
- " 20: [>>und]zwanzig;" +
- " 30: [>>und]dreiu00dfig;" +
- " 40: [>>und]vierzig;" +
- " 50: [>>und]fu00fcnfzig;" +
- " 60: [>>und]sechzig;" +
- " 70: [>>und]siebzig;" +
- " 80: [>>und]achtzig;" +
- " 90: [>>und]neunzig;" +
- " 100: hundert[>%alt-ones>];" +
- " 200: <<hundert[>%alt-ones>];" +
- " 1000: tausend[>%alt-ones>];" +
- " 1100: tausendein[>%alt-ones>];" +
- " 1200: tausend[>%alt-ones>];" +
- " 2000: <<tausend[>%alt-ones>];";
-
- @SmallTest
- public void testString() throws Exception {
- String test = "0123456789";
- String test1 = new String("0123456789"); // different object
- String test2 = new String("0123456780"); // different value
- String offset = new String("xxx0123456789yyy");
- String sub = offset.substring(3, 13);
-
- assertEquals(test, test);
- assertEquals(test, test1);
- assertFalse(test.equals(test2));
-
- assertEquals(0, test.compareTo(test1));
- assertTrue(test1.compareTo(test2) > 0);
- assertTrue(test2.compareTo(test1) < 0);
-
- /* compare string with a nonzero offset, in left/right side */
- assertEquals(0, test.compareTo(sub));
- assertEquals(0, sub.compareTo(test));
- assertEquals(test, sub);
- assertEquals(sub, test);
- /* same base, one is a substring */
- assertFalse(offset.equals(sub));
- assertFalse(sub.equals(offset));
- /* wrong class */
- assertFalse(test.equals(this));
-
- /* null ptr - throw */
- try {
- test.compareTo(null);
- fail("didn't get expected npe");
- } catch (NullPointerException npe) {
- // expected
- }
- /* null ptr - ok */
- assertFalse(test.equals(null));
-
- test = test.substring(1);
- assertEquals("123456789", test);
- assertFalse(test.equals(test1));
-
- test = test.substring(1);
- assertEquals("23456789", test);
-
- test = test.substring(1);
- assertEquals("3456789", test);
-
- test = test.substring(1);
- assertEquals("456789", test);
-
- test = test.substring(3, 5);
- assertEquals("78", test);
-
- test = "this/is/a/path";
- String[] strings = test.split("/");
- assertEquals(4, strings.length);
-
- assertEquals("this is a path", test.replaceAll("/", " "));
- assertEquals("this is a path", test.replace("/", " "));
-
- assertEquals(0, "abc".compareToIgnoreCase("ABC"));
- assertTrue("abc".compareToIgnoreCase("DEF") < 0);
- assertTrue("ABC".compareToIgnoreCase("def") < 0);
- assertTrue("Now".compareTo("Mow") > 0);
- assertTrue("Now".compareToIgnoreCase("Mow") > 0);
-
- // RuleBasedNumberFormnat tests
- RuleBasedNumberFormat format = new RuleBasedNumberFormat();
- format.open(RBNFType.SPELLOUT);
- String result = format.format(15);
- assertEquals("Expected spellout format: 'fifteen' but was "
- + result, "fifteen", result);
- format.close();
-
- format.open(RBNFType.DURATION);
- result = format.format(15);
- assertEquals("Expected spellout format: '15 sec.' but was "
- + result, "15 sec.", result);
- format.close();
-
- format.open(RBNFType.ORDINAL);
- result = format.format(15);
- assertEquals("Expected spellout format: '15th' but was "
- + result, "15th", result);
- format.close();
- format.open(germanSpelloutRule);
-
- result = format.format(1323);
- assertEquals("Expected spellout format: 'tausenddrei" +
- "hundertdreiundzwanzig' but was " + result, "tausend" +
- "dreihundertdreiundzwanzig", result);
- int res = format.parse("tausenddreihundertdreiundzwanzig")
- .intValue();
- assertEquals("Expected spellout format: 'tausend" +
- "dreihundertdreiundzwanzig' but was " + res , 1323, res);
- format.close();
- }
-}
-
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index e8a66c1..2667520 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -670,7 +670,12 @@
public boolean onCreateWindow(WebView view, boolean dialog,
boolean userGesture, Message resultMsg) {
if (!mCanOpenWindows) {
- return false;
+ // We can't open windows, so just send null back.
+ WebView.WebViewTransport transport =
+ (WebView.WebViewTransport) resultMsg.obj;
+ transport.setWebView(null);
+ resultMsg.sendToTarget();
+ return true;
}
// We never display the new window, just create the view and
@@ -688,6 +693,11 @@
resultMsg.sendToTarget();
return true;
}
+
+ @Override
+ public void onCloseWindow(WebView view) {
+ view.destroy();
+ }
};
private static class NewWindowWebView extends WebView {
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index ffc2cbc..f548145 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -121,6 +121,32 @@
}
},
+ new Test("Bad resource #2") {
+ public void run()
+ {
+ Notification n = new Notification(NotificationTestList.this,
+ R.drawable.ic_statusbar_missedcall,
+ null, System.currentTimeMillis()-(1000*60*60*24),
+ "(453) 123-2328",
+ "", null);
+ n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
+ mNM.notify(2, n);
+ }
+ },
+
+ new Test("Bad resource #3") {
+ public void run()
+ {
+ Notification n = new Notification(NotificationTestList.this,
+ R.drawable.ic_statusbar_missedcall,
+ null, System.currentTimeMillis()-(1000*60*60*24),
+ "(453) 123-2328",
+ "", null);
+ n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
+ mNM.notify(3, n);
+ }
+ },
+
new Test("Times") {
public void run()
{
@@ -405,6 +431,16 @@
}
},
+ new Test("Persistent #3") {
+ public void run() {
+ Notification n = new Notification(R.drawable.icon2, "tock tock tock",
+ System.currentTimeMillis());
+ n.setLatestEventInfo(NotificationTestList.this, "Persistent #3",
+ "Notify me!!!", makeIntent());
+ mNM.notify(3, n);
+ }
+ },
+
new Test("Persistent #2 Vibrate") {
public void run() {
Notification n = new Notification(R.drawable.icon2, "tock tock tock",
diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
index 275e5cb6..06506fb 100644
--- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
+++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
@@ -62,6 +62,11 @@
mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ALERTS);
}
},
+ new Test("Disable Ticker") {
+ public void run() {
+ mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_TICKER);
+ }
+ },
new Test("Disable Expand in 3 sec.") {
public void run() {
mHandler.postDelayed(new Runnable() {
@@ -80,7 +85,7 @@
}, 3000);
}
},
- new Test("Disable Both in 3 sec.") {
+ new Test("Disable Expand + Notifications in 3 sec.") {
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {
@@ -90,7 +95,12 @@
}, 3000);
}
},
- new Test("Disable None in 3 sec.") {
+ new Test("Enable everything") {
+ public void run() {
+ mStatusBarManager.disable(0);
+ }
+ },
+ new Test("Enable everything in 3 sec.") {
public void run() {
mHandler.postDelayed(new Runnable() {
public void run() {